An Early Look at the Hedera CLI: Simplify, Automate, and Accelerate Development on Hedera


By Michiel Mulders
The Hedera Command Line Interface (CLI) is a powerful tool designed specifically for developers building and scaling applications on the Hedera network. Whether you need to set up test environments, automate repetitive tasks, or manage your end-to-end Hedera development more efficiently, the Hedera CLI helps you streamline processes and reduce manual effort.
Who Should Try the Hedera CLI?
Automation Seekers: Developers can automate tasks like account creation, token management, and transaction handling to reduce manual effort when building on Hedera.
Hedera Projects: Teams can automate end-to-end testing and continuous integration pipelines across the Hedera testnet, mainnet, previewnet, or localnet environments to bring their products and services to market faster.
Explorers & Newcomers: Great for new users on Hedera who want to experiment with the technology quickly without immediately diving into detailed code development.
Why Use Hedera CLI?
Simplify Environment Management: Set up and manage test environments quickly and without complex configurations.
Easy Integration into Pipelines: Quickly add automation to your existing CI/CD pipelines or script repetitive tasks independently.
Test More Scenarios in Less Time: Run a wide array of test cases with built-in support for Hedera Token Service (HTS), Hedera Consensus Service (HCS), and Hedera Smart Contract Service (HSCS).
Key Features of Hedera CLI
1. Accounts and Token Management
The Hedera CLI simplifies the management of essential Hedera functions such as creating accounts, tokens, associating tokens, and performing token transfers.
Below is a sample command to create an account and set an alias for the account so you can easily reference it in other commands:
hcli account create --alias alice -b 100000000 --type ECDSA
Create and manage tokens (note how we reference Alice and Bob).
// Token create
hcli token create --name mytoken --symbol mtk --decimals 2 --initial-supply 1000 --supply-type infinite --admin-key alice --treasury-id 0.0.5401341 --treasury-key bob
// Token associate
hcli token associate -a,--account-id <accountId> -t,--token-id <tokenId>
// Token transfer
hcli token transfer -t,--token-id <tokenId> --to <to> --from <from> -b,--balance <balance>
2. Automate Hedera Consensus Service (HCS) Operations
The Hedera CLI provides comprehensive functionality for the Hedera Consensus Service. You can create topics, submit messages, and find messages right from the command line:
hcli topic create [-s,--submit-key <submitKey>] [-a,--admin-key <adminKey>] [--memo <memo>]
hcli topic message submit -t,--topic-id <topicId> -m,--message <message>
hcli topic message find -t,--topic-id <topicId> -s,--sequence-number <sequenceNumber>
3. Smart Contracts via Hardhat Integration
The CLI integrates with Hardhat, enabling robust smart contract deployment and interaction directly from your command line. Whether deploying a smart contract or interacting with it, the Hedera CLI makes these actions scriptable. The CLI uses the npx command to execute Hardhat scripts.
npx hardhat run ./dist/contracts/scripts/deploy.js --network local
const stateController = require('../../state/stateController.js').default;
async function main() {
const [deployer] = await ethers.getSigners();
console.log('Deploying contracts with the account:', deployer.address);
// The deployer will also be the owner of our token contract
const ERC721Token = await ethers.getContractFactory('ERC721Token', deployer);
const contract = await ERC721Token.deploy(deployer.address);
await contract.waitForDeployment();
const contractAddress = await contract.getAddress();
console.log('ERC721 Token contract deployed at:', contractAddress);
// Store address in script arguments as "erc721address"
stateController.saveScriptArgument('erc721address', contractAddress);
}
main().catch(console.error);
// SPDX-License-Identifier: MIT
// Compatible with OpenZeppelin Contracts ^5.0.0
pragma solidity ^0.8.22;
import {ERC721} from "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
contract ERC721Token is ERC721, Ownable {
uint256 private _nextTokenId;
constructor(address initialOwner)
ERC721("MyERC721Token", "ERCT2")
Ownable(initialOwner)
{}
function safeMint(address to) public onlyOwner returns (uint256) {
uint256 tokenId = _nextTokenId++;
_safeMint(to, tokenId);
return tokenId;
}
}
Feature Highlight: Script Blocks
Script Blocks allow you to build automated sequences by chaining multiple commands, significantly simplifying environment setup or complex operations.
Additionally, you can store outputs like token or topic IDs, private keys, and other essential details as variables (format: output:varName), which you can reference in subsequent commands. This enables a high degree of automation for simple tasks and complex flows.
Here's a practical example illustrating a complete automation scenario:
Three accounts are created, storing their alias, account ID, and private key.
A new token is created, setting account 1 as the admin key and account 2 as the treasury account, referencing variables with the {{variable}} notation.
Account 3 is associated with the newly created token.
One unit of the token is transferred from the treasury account (account 2) to account 3.
After a 3-second pause, the balance of account 3 is checked, verifying if the token ID is correctly stored in the CLI tool's state.
{
"name": "transfer",
"commands": [
"network use testnet",
"account create -a alice --args privateKey:privKeyAcc1 --args alias:aliasAcc1 --args accountId:idAcc1",
"account create -a bob --args privateKey:privKeyAcc2 --args alias:aliasAcc2 --args accountId:idAcc2",
"account create -a greg --args privateKey:privKeyAcc3 --args alias:aliasAcc3 --args accountId:idAcc3",
"token create -n mytoken -s MTK -d 2 -i 1000 --supply-type infinite --admin-key {{privKeyAcc1}} --treasury-id {{idAcc2}} --treasury-key {{privKeyAcc2}} --args tokenId:tokenId",
"token associate --account-id {{idAcc3}} --token-id {{tokenId}}",
"token transfer -t {{tokenId}} -b 1 --from {{aliasAcc2}} --to {{aliasAcc3}}",
"wait 3",
"account balance --account-id-or-alias {{aliasAcc3}} --token-id {{tokenId}}",
"state view --token-id {{tokenId}}"
],
"args": {}
}
This other example below shows how you can mix hardhat commands and other CLI commands to:
Creates a new account and stores the accountId as aliceAccId.
Compile all contracts.
Deploy a simple storage contract that accepts account IDs.
Call the smart contract to store the account ID created in command 1, stored in the variable aliceAccId.
{
"name": "account-storage",
"commands": [
"account create --alias alice --args accountId:aliceAccId",
"hardhat compile",
"hardhat run ./dist/contracts/scripts/deploy-acc-storage.js",
"hardhat run ./dist/contracts/scripts/add-account-id.js"
],
"args": {}
}
const stateController = require('../../state/stateController.js').default;
async function main() {
const [deployer] = await ethers.getSigners();
console.log('Deploying contracts with the account:', deployer.address);
// The deployer will also be the owner of our token contract
const HederaAccountStorage = await ethers.getContractFactory(
'HederaAccountStorage',
);
const contract = await HederaAccountStorage.deploy();
await contract.waitForDeployment();
const contractAddress = await contract.getAddress();
console.log('HederaAccountStorage contract deployed at:', contractAddress);
// Store address in script arguments as "accountstorageaddress"
stateController.saveScriptArgument('accountstorageaddress', contractAddress);
}
main().catch(console.error);
const stateController = require('../../state/stateController.js').default;
async function main() {
const [deployer] = await ethers.getSigners();
const accountIdToStore = stateController.getScriptArgument('aliceAccId'); // retrieve alice's account ID
const contractAddress = stateController.getScriptArgument(
'accountstorageaddress',
);
// The deployer will also be the owner of our token contract
const HederaAccountStorage = await ethers.getContractFactory(
'HederaAccountStorage',
);
const contract = await HederaAccountStorage.attach(contractAddress);
// Store the account ID in the contract
const tx = await contract.addAccountId(accountIdToStore);
// Wait for the transaction to be mined
await tx.wait();
console.log(`Account ID ${accountIdToStore} successfully stored.`);
}
main().catch(console.error);
Give Feedback
Developers are welcome to explore these Hedera CLI features, test their limits, and provide feedback to improve stability and completeness. Your input is essential as we move towards the official release of the Hedera CLI.
Get Started Now
Check out the GitHub repository and start experimenting with the Hedera CLI today. Share your input and help us build a tool that enhances your development experience on Hedera.
Subscribe to my newsletter
Read articles from Hedera Team directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
