This is the multi-page printable view of this section. Click here to print.

Return to the regular view of this page.

Using Solo

Explore practical applications and integrations for Solo. This section covers using Solo networks with the JavaScript SDK, integrating with external tools, building applications on Hiero consensus nodes, and deploying locally built consensus node binaries for development and testing.

1 - Accessing Solo Services

Learn how to locate and connect to ancillary services deployed alongside Solo networks, including Mirror Node Explorer, Block Nodes, and JSON-RPC Relay services.

1.1 - Using Solo with Mirror Node

Add Mirror Node to a Solo network to stream and query transaction records, account history, and token data via the Hiero Mirror Node REST API.

Overview

The Hiero Mirror Node stores the full transaction history of your local Solo network and exposes it through several interfaces:

  • A web-based block explorer (Hiero Mirror Node Explorer) at http://localhost:38080/localnet/dashboard (Solo 0.63+) or http://localhost:8080/localnet/dashboard (Solo 0.62 and earlier).
  • A REST API via the mirror-ingress service at http://localhost:38081 (Solo 0.63+) or http://localhost:8081 (Solo 0.62 and earlier) (recommended entry point — routes to the correct REST implementation).
  • A gRPC endpoint for mirror node subscriptions.

Important: The port numbers in this document are Solo’s default targets. If any port is already in use on your machine when Solo starts, Solo automatically selects the next available port. If an endpoint does not work, check the actual ports assigned to your deployment — see Port Reference below.

This guide walks you through adding Mirror Node and the Hiero Explorer to a Solo network, and shows you how to query transaction data and create accounts.


Prerequisites

Before proceeding, ensure you have completed the following:

  • System Readiness - your local environment meets all hardware and software requirements, including Docker and Solo.
  • Quickstart - you have a running Solo network deployed using solo one-shot single deploy.
  • To find your deployment name at any time, run solo one-shot show deployment (see Capture your deployment name).

Step 1: Deploy Solo with Mirror Node

Note: If you deployed your network using one-shot, Falcon, or the Task Tool, Mirror Node is already running - skip to Step 2: Access the Mirror Node Explorer.

Fresh manual Deployment

If you are building a custom network or adding the mirror node to an existing deployment, run the following commands in sequence.

On native Windows (PowerShell), set the environment variables with $env: instead of export (and reference them as $env:SOLO_CLUSTER_NAME, etc., in the commands that follow):

$env:SOLO_CLUSTER_NAME = 'solo-cluster'
$env:SOLO_NAMESPACE = 'solo-deployment'
$env:SOLO_CLUSTER_SETUP_NAMESPACE = 'solo-cluster-setup'
$env:SOLO_DEPLOYMENT = 'solo-deployment'
# Set environment variables
export SOLO_CLUSTER_NAME=solo-cluster
export SOLO_NAMESPACE=solo-deployment
export SOLO_CLUSTER_SETUP_NAMESPACE=solo-cluster-setup
export SOLO_DEPLOYMENT=solo-deployment

# Reset environment
rm -Rf ~/.solo
kind delete cluster -n "${SOLO_CLUSTER_NAME}"
kind create cluster -n "${SOLO_CLUSTER_NAME}"

# Configure cluster
solo cluster-ref config setup \
  --cluster-setup-namespace "${SOLO_CLUSTER_SETUP_NAMESPACE}"
solo cluster-ref config connect \
  --cluster-ref ${SOLO_CLUSTER_NAME} \
  --context kind-${SOLO_CLUSTER_NAME}

# Create deployment
solo deployment config create \
  --namespace "${SOLO_NAMESPACE}" \
  --deployment "${SOLO_DEPLOYMENT}"
solo deployment cluster attach \
  --deployment "${SOLO_DEPLOYMENT}" \
  --cluster-ref ${SOLO_CLUSTER_NAME} \
  --num-consensus-nodes 2

# Generate keys and deploy consensus nodes
solo keys consensus generate \
  --deployment "${SOLO_DEPLOYMENT}" \
  --gossip-keys --tls-keys \
  -i node1,node2
solo consensus network deploy --deployment "${SOLO_DEPLOYMENT}" -i node1,node2
solo consensus node setup --deployment "${SOLO_DEPLOYMENT}" -i node1,node2
solo consensus node start --deployment "${SOLO_DEPLOYMENT}" -i node1,node2

# Add mirror node and explorer
solo mirror node add \
  --deployment "${SOLO_DEPLOYMENT}" \
  --cluster-ref ${SOLO_CLUSTER_NAME} \
  --enable-ingress \
  --pinger
solo explorer node add \
  --deployment "${SOLO_DEPLOYMENT}" \
  --cluster-ref ${SOLO_CLUSTER_NAME}

Note: The --pinger flag in solo mirror node add starts a background service that sends transactions to the network at regular intervals. This is required because mirror node record files are only imported when a new record file is created - without it, the mirror node will appear empty until the next transaction occurs naturally.


Step 2: Access the Mirror Node Explorer

Once Mirror Node is running, open the Hiero Explorer in your browser at:

http://localhost:38080/localnet/dashboard

Note: If you are using Solo 0.62 or earlier, the Explorer is at http://localhost:8080/localnet/dashboard. If that port does not work, check the actual port assigned to your deployment — see Port Reference.

The Explorer lets you browse accounts, transactions, tokens, and contracts on your Solo network in real time.


Step 3: Create Accounts and View Transactions

Create test accounts and observe them appearing in the Explorer:

solo ledger account create --deployment solo-deployment --hbar-amount 100
solo ledger account create --deployment solo-deployment --hbar-amount 100

Open the Explorer at http://localhost:38080/localnet/dashboard (Solo 0.63+) or http://localhost:8080/localnet/dashboard (Solo 0.62 and earlier) to see the new accounts and their transactions recorded by the Mirror Node. If the port does not work, check your actual port assignments — see Port Reference.

You can also use the Hiero JavaScript SDK to create a topic, submit a message, and subscribe to it.


Step 4: Access Mirror Node APIs

Option A: Mirror-Ingress (localhost:38081)

Use localhost:38081 (Solo 0.63+) for all Mirror Node REST API access. The mirror-ingress service routes requests to the correct REST implementation automatically. This is important because certain endpoints are only supported in the newer rest-java version.

# List recent transactions
curl -s "http://localhost:38081/api/v1/transactions?limit=5"

# Get account details
curl -s "http://localhost:38081/api/v1/accounts/0.0.2"

Note: If you are using Solo 0.62 or earlier, use localhost:8081 instead of localhost:38081. localhost:5551 (the legacy Mirror Node REST API direct endpoint) is being phased out. Always use the mirror-ingress port to ensure compatibility with all endpoints.

If you need to access it directly:

kubectl port-forward svc/mirror-1-rest -n "${SOLO_NAMESPACE}" 5551:80 &
curl -s "http://${REST_IP:-127.0.0.1}:5551/api/v1/transactions?limit=1"

Option B: Mirror Node gRPC

For mirror node gRPC subscriptions (e.g. topic messages, account balance updates), enable port-forwarding manually if not already active:

kubectl port-forward svc/mirror-1-grpc -n "${SOLO_NAMESPACE}" 5600:5600 &

Then verify available services:

grpcurl -plaintext "${GRPC_IP:-127.0.0.1}:5600" list

Option C: Mirror Node REST-Java (Direct Access)

For direct access to the rest-java service (bypassing the ingress):

kubectl port-forward service/mirror-1-restjava -n "${SOLO_NAMESPACE}" 8084:80 &

# Example: NFT allowances
curl -s "http://${REST_IP:-127.0.0.1}:8084/api/v1/accounts/0.0.2/allowances/nfts"

In most cases you should use localhost:38081 (Solo 0.63+) or localhost:8081 (Solo 0.62 and earlier) instead.


Port Reference

The ports listed below are Solo’s default targets. Solo checks each port before opening a tunnel - if the port is already in use, Solo picks the next available one and logs Using available port <port>. If an endpoint is not reachable, check the actual ports your deployment is using with solo deployment config ports --deployment <deployment-name> - see Port availability for the full set of commands.

The default local ports depend on your Solo version:

Solo 0.63 and later (current defaults):

ServiceLocal PortAccess Method
Hiero Explorer38080Browser (--enable-ingress)
Mirror Node (all-in-one)38081HTTP (--enable-ingress)
Mirror Node REST API5551kubectl port-forward (manual)
Mirror Node gRPC5600kubectl port-forward
Mirror Node REST Java8084kubectl port-forward

Solo 0.62 and earlier:

ServiceLocal PortAccess Method
Hiero Explorer8080Browser (--enable-ingress)
Mirror Node (all-in-one)8081HTTP (--enable-ingress)
Mirror Node REST API5551kubectl port-forward
Mirror Node gRPC5600kubectl port-forward
Mirror Node REST Java8084kubectl port-forward

Restoring Port-Forwards

If port-forwards are interrupted — for example after a system restart — restore them by re-running the relevant component add commands. These commands are idempotent and will reattach port-forwards without redeploying:

solo mirror node add --deployment "${SOLO_DEPLOYMENT}"
solo explorer node add --deployment "${SOLO_DEPLOYMENT}"

Tearing Down

To remove the Mirror Node from a running deployment:

solo mirror node destroy --deployment "${SOLO_DEPLOYMENT}" --force

To remove the Hiero Mirror Node Explorer:

solo explorer node destroy --deployment "${SOLO_DEPLOYMENT}" --force

For full network teardown, see Step-by-Step Manual Deployment-Cleanup.

2 - Using Solo with Hiero SDKs

Walk through submitting your first transaction to a local Solo network using the Hiero JavaScript, Java, or Go SDK.

Overview

The Hiero SDKs let you build and test applications on the Hiero network using JavaScript / TypeScript, Java, or Go. This guide walks you through launching a local Solo network, locating its bootstrap operator account, setting up a project for your chosen SDK, and running example transactions. The Solo-side steps are identical across all three SDKs; the language-specific steps appear in tabs you can switch between.


Prerequisites

Before proceeding, ensure you have completed the following:

  • System Readiness:

    • Your local environment meets all hardware and software requirements, including Docker, kubectl, and Solo.

    • The shared baseline tools:

      RequirementVersionPurpose
      Docker DesktopLatestRuns the Solo cluster containers
      SoloLatest stableDeploys and manages the local network
  • Plus the SDK-specific toolchain for the language you’ll use:

    RequirementVersionPurpose
    Node.jsv20 or higherRuns your application against the SDK
    RequirementVersionPurpose
    JDKv21 or higher (Eclipse Temurin recommended)Required by the Hiero Java SDK
    Gradlev8.5 or laterBuilds and runs the Java project
    RequirementVersionPurpose
    Gov1.25 or higherRequired by the Hiero Go SDK

Note: Solo uses Docker Desktop to spin up local Hiero consensus and mirror nodes. Ensure Docker Desktop is running before deploying the local network.


Step 1: Launch a Local Solo Network

Deploy a local Solo network by following the Solo Quickstart. Once it’s running, retrieve your deployment name with solo one-shot show deployment - the rest of this guide refers to it as <your-deployment-name>.


Step 2: Install the SDK

Install the Hiero SDK for your language.

Initialize a Node.js project and install the SDK from npm:

mkdir solo-js-demo && cd solo-js-demo
npm init -y
npm install @hiero-ledger/sdk

For the full SDK source tree (with the bundled examples/ directory), follow the Hiero JavaScript SDK README. The repository uses pnpm workspaces + go-task to build.

Follow the Hiero Java SDK quickstart to set up a Gradle (or Maven) project. The minimum dependency set:

implementation("com.hedera.hashgraph:sdk:2.72.0")
implementation("io.grpc:grpc-netty-shaded:1.64.0")
implementation("org.slf4j:slf4j-nop:2.0.9")

Initialize a Go module and add the Hiero Go SDK:

mkdir solo-go-demo && cd solo-go-demo
go mod init solo-go-demo
go get github.com/hiero-ledger/hiero-sdk-go/v2@v2.80.0

Go 1.25 or higher is required (per go.mod in the SDK). See the Hiero Go SDK README for the full setup walkthrough.


Step 3: Locate Your Operator Credentials

solo one-shot single deploy provisions a bootstrap operator account (0.0.2) at genesis and writes its credentials to disk. Use this account as your operator - you do not need to call solo ledger account create for the examples in this guide.

  • Print the operator credentials:

    cat ~/.solo/one-shot-<your-deployment-name>/accounts.json
    
  • Expected output:

    {
      "systemAccounts": [
        {
          "name": "Operator",
          "accountId": "0.0.2",
          "publicKey": "302a300506032b65700321000aa8e21064c61eab86e2a9c164565b4e7a9a4146106e0a6cd03a8c395a110e92",
          "privateKey": "302e020100300506032b65700422042091132178e72057a1d7528025956fe39b0b847f200ab59b2fdd367017f3087137"
        }
      ],
      "createdAccounts": [
        { "accountId": "0.0.1002", "privateKey": "0x105d050185...", "balance": "1000000 ℏ", "group": "ecdsa-alias" },
        ...
      ]
    }
    

    The systemAccounts[0] block is the operator (0.0.2) with an Ed25519 key in DER format. The createdAccounts array contains additional pre-funded ECDSA-alias accounts useful for EVM workflows.

  • Save the accountId and privateKey values from systemAccounts[0] - you will configure the SDK with them in the next step.

EVM tooling note: For ethers.js, Hardhat, or Foundry, use one of the createdAccounts entries; their privateKey values are already in 0x-prefixed hex form. See Using Solo with EVM Tools for the full EVM-side workflow.


Step 4: Configure the SDK to Connect to Solo

Each SDK reads operator credentials and the network endpoint differently. Pick your language tab below.

The Hiero JavaScript SDK uses environment variables to authenticate the operator account. Create a .env file at the root of the hiero-sdk-js directory:

cd hiero-sdk-js

cat > .env <<EOF
# Operator account ID (systemAccounts[0].accountId from Step 3)
OPERATOR_ID="0.0.2"

# Operator private key (systemAccounts[0].privateKey from Step 3)
OPERATOR_KEY="302e020100300506032b65700422042091132178e72057a1d7528025956fe39b0b847f200ab59b2fdd367017f3087137"

# Required by LocalProvider in the SDK example scripts (Step 5).
# Not used by the Client.fromConfig() snippet below, but harmless to include.
HEDERA_NETWORK="local-node"
EOF

source .env

Important: OPERATOR_KEY must be set to the privateKey value, not the publicKey. The private key is the longer DER-encoded string beginning with 302e.... The example scripts also require HEDERA_NETWORK - they throw “LocalProvider requires the HEDERA_NETWORK environment variable to be set” if it is missing.

Security: Never commit .env to source control - the file holds the operator’s private key. Add .env to your repository’s .gitignore.

Configure the client with the Solo 0.63+ port-forwards using Client.fromConfig(). The SDK’s built-in Client.forLocalNode() preset is hardcoded to localhost:50211, which does not match Solo 0.63+ defaults (localhost:35211 for consensus gRPC, localhost:38081 for the mirror node ingress). Use the explicit network map shown below instead:

import { Client, AccountId } from "@hiero-ledger/sdk";

const network = { "127.0.0.1:35211": AccountId.fromString("0.0.3") };
const mirrorNetwork = "127.0.0.1:38081";

const client = Client.fromConfig({
  network,
  mirrorNetwork,
  // Required: the SDK's address-book refresh otherwise pulls in the
  // hardcoded 50211/50212 ports, which Solo 0.63+ does not expose.
  scheduleNetworkUpdate: false,
});
client.setOperator(process.env.OPERATOR_ID!, process.env.OPERATOR_KEY!);

Create a .env file at the project root to hold your operator credentials:

cat > .env <<EOF
# Operator account ID (systemAccounts[0].accountId from Step 3)
OPERATOR_ID=0.0.2

# Operator private key (systemAccounts[0].privateKey from Step 3)
OPERATOR_KEY=302e020100300506032b65700422042091132178e72057a1d7528025956fe39b0b847f200ab59b2fdd367017f3087137
EOF

Important: OPERATOR_KEY must be the privateKey value, not the publicKey. The private key is the longer DER-encoded string beginning with 302e....

Security: Never commit .env to source control - the file holds the operator’s private key. Add .env to your repository’s .gitignore.

Load the env variables:

set -a; source .env; set +a

Configure the client in src/main/java/Main.java. Unlike the JavaScript SDK (which ships a LocalProvider / Client.forName("local-node") preset), the Hiero Java SDK has no local-node preset - Client.forName(...) only accepts "mainnet", "testnet", or "previewnet" and throws IllegalArgumentException otherwise. Build the network map explicitly using Client.forNetwork(Map) against Solo’s auto-forwarded ports:

import com.hedera.hashgraph.sdk.AccountId;
import com.hedera.hashgraph.sdk.AccountInfoQuery;
import com.hedera.hashgraph.sdk.Client;
import com.hedera.hashgraph.sdk.PrivateKey;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class Main {
    public static void main(String[] args) throws Exception {
        String operatorId  = System.getenv("OPERATOR_ID");
        String operatorKey = System.getenv("OPERATOR_KEY");
        if (operatorId == null || operatorKey == null) {
            throw new IllegalStateException(
                    "Set OPERATOR_ID and OPERATOR_KEY env vars before running.");
        }

        Map<String, AccountId> network = new HashMap<>();
        network.put("127.0.0.1:35211", AccountId.fromString("0.0.3"));

        Client client = Client.forNetwork(network);
        client.setMirrorNetwork(List.of("127.0.0.1:38081"));
        client.setOperator(
                AccountId.fromString(operatorId),
                PrivateKey.fromString(operatorKey));

        // ... transactions and queries go here ...

        client.close();
    }
}

Client.setMirrorNetwork(List<String>) declares throws InterruptedException, and queries/transactions throw TimeoutException / PrecheckStatusException. Declaring throws Exception on main keeps the example readable; wrap with explicit try/catch in production code.

The Hiero Go SDK reads operator credentials and the target network from environment variables.

Create a .env file at the project root:

cat > .env <<'EOF'
# Operator account ID (systemAccounts[0].accountId from Step 3)
export OPERATOR_ID="0.0.2"

# Operator private key (systemAccounts[0].privateKey from Step 3)
export OPERATOR_KEY="302e020100300506032b65700422042091132178e72057a1d7528025956fe39b0b847f200ab59b2fdd367017f3087137"

# Network name. "localhost" is the SDK's local-node preset; only used by
# ClientForName(...) - the explicit ClientForNetworkV2 path below ignores it.
export HEDERA_NETWORK="localhost"
EOF

source .env

Important: OPERATOR_KEY must be the privateKey value, not the publicKey. The private key is the longer DER-encoded string beginning with 302e....

Security: Never commit .env to source control. Add it to .gitignore.

Configure the client in main.go. The Hiero Go SDK ships a ClientForName preset that recognizes "local" and "localhost", but the preset is hardcoded to 127.0.0.1:50211 (consensus gRPC) and 127.0.0.1:5600 (mirror gRPC) - ports Solo does not expose by default (Solo’s auto-port-forwards use 35211 and 38081). The simplest path that works out of the box is to build the network map explicitly with ClientForNetworkV2:

package main

import (
    "fmt"
    "os"

    hiero "github.com/hiero-ledger/hiero-sdk-go/v2/sdk"
)

func main() {
    operatorID := os.Getenv("OPERATOR_ID")
    operatorKey := os.Getenv("OPERATOR_KEY")
    if operatorID == "" || operatorKey == "" {
        fmt.Fprintln(os.Stderr, "Set OPERATOR_ID and OPERATOR_KEY env vars before running.")
        os.Exit(1)
    }

    network := map[string]hiero.AccountID{
        "127.0.0.1:35211": {Account: 3},
    }

    client, err := hiero.ClientForNetworkV2(network)
    if err != nil {
        panic(err)
    }
    defer client.Close()

    client.SetMirrorNetwork([]string{"127.0.0.1:38081"})

    opAccID, err := hiero.AccountIDFromString(operatorID)
    if err != nil {
        panic(err)
    }
    opKey, err := hiero.PrivateKeyFromString(operatorKey)
    if err != nil {
        panic(err)
    }
    client.SetOperator(opAccID, opKey)

    // ... transactions and queries go here ...
}

The network map’s value uses the struct literal hiero.AccountID{Account: 3} (matches network-node1’s 0.0.3). This is shorter than hiero.AccountIDFromString("0.0.3") and matches the form upstream examples use.

Verify your configuration

Add an AccountInfoQuery for the operator and run it to confirm the client reaches Solo.

In your TypeScript/JavaScript code, after building client:

const info = await new AccountInfoQuery()
  .setAccountId(AccountId.fromString(process.env.OPERATOR_ID!))
  .execute(client);

console.log("Account ID :", info.accountId.toString());
console.log("Balance    :", info.balance.toString());

In Main.java, after setOperator(...):

var info = new AccountInfoQuery()
        .setAccountId(AccountId.fromString(operatorId))
        .execute(client);

System.out.println("Account ID : " + info.accountId);
System.out.println("Balance    : " + info.balance);

Then run gradle run.

Expected output:

Account ID : 0.0.2
Balance    : 49989999499.9946 ℏ

In main.go, after SetOperator(...):

info, err := hiero.NewAccountInfoQuery().
    SetAccountID(opAccID).
    Execute(client)
if err != nil {
    panic(err)
}
fmt.Printf("Account ID : %s\n", info.AccountID)
fmt.Printf("Balance    : %s\n", info.Balance)

Then run go run ..

Expected output:

Account ID : 0.0.2
Balance    : 4.99899994792001e+10 ℏ

The Hbar value is printed in scientific notation by default; format it with info.Balance.As(hiero.HbarUnits.Hbar) or info.Balance.AsTinybar() for decimal HBAR or tinybars.

The exact balance differs slightly between deployments; what matters is that an AccountInfo returns at all - that proves the gRPC pipe to the consensus node is alive and the operator credentials are valid.


Step 5: Run Transactions Against Solo

Heads up (JavaScript and Go only): The SDK example programs use the SDK’s local-node preset (LocalProvider in JS, ClientForName("localhost") in Go), which is hardcoded to 127.0.0.1:50211 (consensus gRPC) and 127.0.0.1:5600 (mirror gRPC). Solo doesn’t expose those ports by default, so the upstream example programs cannot reach the network out of the box. Either follow Make the examples reachable below, or rewrite the example to use the Client.fromConfig (JS) / ClientForNetworkV2 (Go) pattern from Step 4. The Java SDK has no local-node preset, so this caveat does not apply there.

Make the examples reachable

Pick one:

Option A - forward Solo’s services to the SDK’s legacy ports (run examples unchanged):

kubectl port-forward svc/haproxy-node1-svc -n <your-deployment-name> 50211:50211 &
kubectl port-forward svc/mirror-1-grpc     -n <your-deployment-name> 5600:5600 &

The kubectl namespace matches <your-deployment-name> for default one-shot deploys.

Option B - edit the example to use the Client.fromConfig({ ..., scheduleNetworkUpdate: false }) pattern from Step 4. No port-forwarding needed.

No additional setup required. The Client.forNetwork(Map) pattern from Step 4 hits Solo’s auto-forwarded ports (35211 consensus, 38081 mirror) directly.

Pick one:

Option A - forward Solo’s services to the SDK’s legacy ports (run upstream examples with HEDERA_NETWORK="localhost" unchanged):

kubectl port-forward svc/haproxy-node1-svc -n <your-deployment-name> 50211:50211 &
kubectl port-forward svc/mirror-1-grpc     -n <your-deployment-name> 5600:5600 &

Option B - use ClientForNetworkV2 directly (the pattern shown in Step 4). No port-forwarding needed.

Try a tutorial against your Solo network

Once your client is configured (Step 4), the canonical Hiero / Hedera SDK tutorials run against Solo the same way they run against testnet or mainnet - only the network endpoint changes. Pick a tutorial and follow it as written:

Each SDK also ships a runnable examples/ directory with dozens of additional patterns - token creation, smart contract deployment, HCS pub/sub, scheduled transactions, and more.

Verify transactions you submit in the Hiero Explorer: http://localhost:38080/localnet/dashboard.


Step 6: Tear Down the Network

When you are finished, remove the local consensus node, mirror node, block node, relay, explorer, and all data volumes:

solo one-shot single destroy \
  --deployment <your-deployment-name>

Optional: Manage Files on the Network

Solo provides CLI commands to create and update files stored on the Hiero File Service.

Create a New File

solo ledger file create \
  --deployment <your-deployment-name> \
  --file-path ./config.json

This command:

  • Creates a new file on the network and returns a system-assigned file ID.
  • Automatically splits files larger than 4 KB into chunks using FileAppendTransaction.
  • Verifies that the uploaded content matches the local file.

Update an Existing File

solo ledger file update \
  --deployment <your-deployment-name> \
  --file-id 0.0.1234 \
  --file-path ./updated-config.json

This command:

  • Verifies the file exists on the network (errors if not found).
  • Replaces the file content and re-verifies the upload.
  • Automatically handles chunking for large files (>4 KB).

Note: For files larger than 4 KB, both commands split content into 4 KB chunks and display per-chunk progress during the append phase.


Inspect Transactions in Hiero Explorer

Open the Hiero Explorer to visually inspect submitted transactions, accounts, topics, and files. The Solo Quickstart’s Access your local network section lists the Explorer URL and port-availability behavior. Once it’s open, search by account ID, transaction ID, or topic ID to confirm that your transactions reached consensus.


Retrieving Logs

Solo writes logs to ~/.solo/logs/:

Log FileContents
solo.logHuman-readable Solo CLI output and lifecycle events
solo.ndjsonNewline-delimited JSON of the same events (authoritative, machine-readable)

The Solo log is useful for debugging connectivity issues between the SDK and your local Solo network.

SDK logging

For SDK-side logs (which logger each SDK uses and how to configure it), see the upstream docs:


Troubleshooting

SymptomLikely CauseFix
LocalProvider requires the HEDERA_NETWORK environment variable to be set (JS)HEDERA_NETWORK missing from .env, or .env not sourced in this shellAdd HEDERA_NETWORK="local-node" to .env; then source .env
Dependency resolution is looking for a library compatible with JVM runtime version 17, but 'com.hedera.hashgraph:sdk:2.72.0' is only compatible with JVM runtime version 21 or newer (Java)JDK 17 target in build.gradle.ktsSet sourceCompatibility = JavaVersion.VERSION_21 and ensure the JDK on PATH is v21+
IllegalArgumentException: Name must be one-of 'mainnet', 'testnet', or 'previewnet' (Java)Called Client.forName("local-node")Use Client.forNetwork(Map) + setMirrorNetwork(List); Java SDK has no local-node preset
go: module ... requires go >= 1.25 (Go)Local Go is older than the SDK’s go.mod floorUpgrade Go to v1.25+; on macOS, brew install go
TimeoutException from query or transaction (all SDKs)Consensus node not actually serving (deploy reported a NodesStarted timeout even though the pod is Running)Run solo one-shot single destroy --deployment <name> then solo one-shot single deploy; the second attempt usually succeeds
SDK calls fail; 127.0.0.1:35211 shows as not listening (all SDKs)Solo’s auto-port-forward for consensus gRPC died after deployRestore manually: kubectl port-forward svc/haproxy-node1-svc -n <your-deployment-name> 35211:50211 &
Upstream SDK example hangs against Solo (JS, Go)Example uses the SDK’s local-node preset, hardcoded to ports Solo doesn’t exposeFollow Option A or B in Make the examples reachable
INVALID_SIGNATURE receipt errorOPERATOR_KEY set to public key instead of private keyRe-check your .env - use the privateKey field value
INSUFFICIENT_TX_FEEOperator account has no HBARUse a pre-funded createdAccounts entry or top up the operator

Resources

3 - Using Solo with EVM Tools

Point your existing Ethereum tooling (Hardhat, ethers.js, and MetaMask) at a local Hiero network via the Hiero JSON-RPC relay. This document covers enabling the relay, creating and configuring a Hardhat project, deploying a Solidity contract, and configuring wallets.

Overview

Hiero is EVM-compatible. The Hiero JSON-RPC relay exposes a standard Ethereum JSON-RPC interface on your local Solo network, letting you use familiar EVM tools without modification.

This guide walks you through:

  • Launching a Solo network with the JSON-RPC relay enabled.
  • Retrieving ECDSA accounts for EVM tooling.
  • Creating and configuring a Hardhat project against the relay.
  • Deploying and interacting with a Solidity contract.
  • Verifying transactions via the Explorer and Mirror Node.
  • Configuring ethers.js and MetaMask.

Prerequisites

Before proceeding, ensure you have completed the following:

  • System Readiness - your local environment meets all hardware and software requirements, including Docker and Solo.
  • Quickstart - you are comfortable running Solo deployments.

You will also need:


Step 1: Launch a Solo Network with the JSON-RPC Relay

The easiest way to start a Solo network with the relay pre-configured is via one-shot single deploy, which provisions the consensus node, mirror node, Hiero Mirror Node Explorer, and the Hiero JSON-RPC relay in a single step:

npx @hiero-ledger/solo one-shot single deploy

This command:

  • Creates a local Kind Kubernetes cluster.
  • Deploys a Hiero consensus node, mirror node, and Hiero Mirror Node Explorer.
  • Deploys the Hiero JSON-RPC relay and exposes it at http://localhost:37546 (Solo 0.63+).
  • Generates three groups of pre-funded accounts, including ECDSA (EVM-compatible) accounts.

Relay endpoint summary (Solo 0.63 and later):

PropertyValue
RPC URLhttp://localhost:37546
Chain ID298
Currency symbolHBAR

If you are using Solo 0.62 or earlier, the relay is at http://localhost:7546.

Adding the Relay to an Existing Deployment

If you already have a running Solo network without the relay, see Step 10: Deploy JSON-RPC Relay in the Step-by-Step Manual Deployment guide for full instructions, then return here once your relay is running on http://localhost:37546 (Solo 0.63+) or http://localhost:7546 (Solo 0.62 and earlier).

To remove the relay when you no longer need it, see Cleanup Step 1: Destroy JSON-RPC Relay in the same guide.


Step 2: Retrieve Your ECDSA Account and Private Key

one-shot single deploy creates ECDSA alias accounts, which are required for EVM tooling such as Hardhat, ethers.js, and MetaMask. These accounts and their private keys are saved to a cache directory on completion.

Note: ED25519 accounts are not compatible with Hardhat, ethers.js, or MetaMask when used via the JSON-RPC interface. Always use the ECDSA keys from accounts.json for EVM tooling.

  • To find your deployment name, run solo one-shot show deployment (see Capture your deployment name).

  • Then open the accounts file at:

    ~/.solo/one-shot-<deployment-name>/accounts.json
    
  • Open that file to retrieve your ECDSA keys and EVM address. Each account entry contains:

    • An ECDSA private key - 64 hex characters with a 0x prefix (e.g. 0x105d0050...).
    • An ECDSA public key - the corresponding public key.
    • An EVM address - derived from the public key (e.g. 0x70d379d473e2005bb054f50a1d9322f45acb215a). In Hiero terminology, this means the account has an EVM address aliased from its ECDSA public key.
    0x105d0050185ccb907fba04dd92d8de9e32c18305e097ab41dadda21489a211524
    0x2e1d968b041d84dd120a5860cee60cd83f9374ef527ca86996317ada3d0d03e7
    ...
    
  • Export the private key for one account as an environment variable - never hardcode private keys in source files:

export SOLO_EVM_PRIVATE_KEY="0x105d0050185ccb907fba04dd92d8de9e32c18305e097ab41dadda21489a211524"
$env:SOLO_EVM_PRIVATE_KEY = '0x105d0050185ccb907fba04dd92d8de9e32c18305e097ab41dadda21489a211524'

Step 3: Create and Configure a Hardhat Project

A ready-to-run Hardhat project is provided in the Solo repository. Skip to Step 4 after cloning:

git clone https://github.com/hiero-ledger/solo.git
cd solo/examples/hardhat-with-solo/hardhat-example
npm install

Option B: Create a New Hardhat Project from Scratch

If you want to integrate Solo into your own project:

mkdir solo-hardhat && cd solo-hardhat
npm init -y
npm install --save-dev hardhat @nomicfoundation/hardhat-toolbox
npx hardhat init

When prompted, choose TypeScript project or JavaScript project based on your preference.

Install dependencies:

npm install

Configure Hardhat to Connect to the Solo Relay

Create or update hardhat.config.ts to point at the Solo JSON-RPC relay. The chainId of 298 is required - Hardhat will reject transactions if it does not match the network:

import { defineConfig } from "hardhat/config";
import hardhatToolboxMochaEthers from "@nomicfoundation/hardhat-toolbox-mocha-ethers";

const config = defineConfig({
  plugins: [hardhatToolboxMochaEthers],
  solidity: "0.8.28",
  networks: {
    my_solo_deployment: {
      type: "http",
      url: "http://127.0.0.1:37546",
      chainId: 298,
      // Load from environment - never commit private keys to source control
      accounts: process.env.SOLO_EVM_PRIVATE_KEY
        ? [process.env.SOLO_EVM_PRIVATE_KEY]
        : [],
    },
  },
});

export default config;

Important: This is the Hardhat v3 config format used by the bundled example (Hardhat 3.x). Each network needs an explicit type: "http", and chainId: 298 must be set - without type/chainId, Hardhat v3 fails with HHE40000: No network with chain id "298" found when connecting to the relay. The network key (my_solo_deployment) must match the --network flag you pass to Hardhat commands.


Step 4: Deploy and Interact with a Solidity Contract

The Sample Contract

If using the pre-built Solo example, contracts/SimpleStorage.sol is included. For a new project, create contracts/SimpleStorage.sol:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;

contract SimpleStorage {
    uint256 private value;

    event ValueChanged(
        uint256 indexed oldValue,
        uint256 indexed newValue,
        address indexed changer
    );

    constructor(uint256 initial) {
        value = initial;
    }

    function get() external view returns (uint256) {
        return value;
    }

    function set(uint256 newValue) external {
        uint256 old = value;
        value = newValue;
        emit ValueChanged(old, newValue, msg.sender);
    }
}

Compile the Contract

npx hardhat compile

Expected output:

Compiled 1 Solidity file successfully (evm target: paris).

Run the Tests

npx hardhat test --network my_solo_deployment

For the pre-built example, the test suite covers three scenarios:

  SimpleStorage
    ✔ deploys with initial value
    ✔ updates value and emits ValueChanged event
    ✔ allows other accounts to set value

  3 passing (12s)

Deploy via a Script

To deploy SimpleStorage to your Solo network using a deploy script:

npx hardhat run scripts/deploy.ts --network my_solo_deployment

A minimal scripts/deploy.ts looks like:

Hardhat v3: The bundled example pins Hardhat 3.x, which removed the ethers named export from the hardhat module. Obtain ethers from the network connection with const { ethers } = await network.connect() instead of import { ethers } from "hardhat".

import { network } from "hardhat";

async function main() {
  const { ethers } = await network.connect();
  const SimpleStorage = await ethers.getContractFactory("SimpleStorage");
  const contract = await SimpleStorage.deploy(42);
  await contract.waitForDeployment();

  console.log("SimpleStorage deployed to:", await contract.getAddress());
}

main().catch((err) => {
  console.error(err);
  process.exit(1);
});

Step 5: Send a Transaction with ethers.js

To submit a transaction directly from a script using ethers.js via Hardhat:

import { network } from "hardhat";

async function main() {
  const { ethers } = await network.connect();
  const [sender] = await ethers.getSigners();
  console.log("Sender:", sender.address);

  const balance = await ethers.provider.getBalance(sender.address);
  console.log("Balance:", ethers.formatEther(balance), "HBAR");

  const tx = await sender.sendTransaction({
    to: sender.address,
    value: 10_000_000_000n,
  });

  await tx.wait();
  console.log("Transaction confirmed. Hash:", tx.hash);
}

main().catch((err) => {
  console.error(err);
  process.exit(1);
});

Run it with:

npx hardhat run scripts/send-tx.ts --network my_solo_deployment

Step 6: Verify Transactions

Confirm your transactions reached consensus using any of the following:

Hiero Mirror Node Explorer

http://localhost:38080/localnet/dashboard

Note: If you are using Solo 0.62 or earlier, the Explorer is at http://localhost:8080/localnet/dashboard.

Search by account address, transaction hash, or contract address to view transaction details and receipts.

Hiero Mirror Node REST API

http://localhost:38081/api/v1/transactions?limit=5

Returns the five most recent transactions in JSON format. Useful for scripted verification.

Note: localhost:5551 (the legacy Mirror Node REST API direct endpoint) is being phased out. Use localhost:38081 (Solo 0.63+) or localhost:8081 (Solo 0.62 and earlier) to ensure compatibility with all endpoints.

Hiero JSON RPC Relay (eth_getTransactionReceipt)

curl -X POST http://localhost:37546 \
  -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","method":"eth_getTransactionReceipt","params":["0xYOUR_TX_HASH"],"id":1}'

Step 7: Configure MetaMask

To connect MetaMask to your local Solo network:

  1. Open MetaMask and go to Settings → Networks → Add a network → Add a network manually.

  2. Enter the following values:

    FieldValue
    Network nameSolo Local
    New RPC URLhttp://localhost:37546
    Chain ID298
    Currency symbolHBAR

    Note: If you are using Solo 0.62 or earlier, use http://localhost:7546 for the RPC URL.

  3. Click Save and switch to the Solo Local network.

  4. Import an account using an ECDSA private key from accounts.json:

    • Click the account icon → Import account.
    • Paste the private key (with 0x prefix).
    • Click Import.

Your MetaMask wallet is now connected to the local Solo network and funded with the pre-allocated HBAR balance.


Step 8: Tear Down the Network

When finished, destroy the Solo deployment and all associated containers:

npx @hiero-ledger/solo one-shot single destroy

If you added the relay manually to an existing deployment:

solo relay node destroy --deployment "${SOLO_DEPLOYMENT}"

Reference: Running the Full Example Automatically

The hardhat-with-solo example includes a Taskfile.yml that automates all steps - deploy network, install dependencies, compile, and test - in a single command:

cd solo/examples/hardhat-with-solo
task

To tear everything down:

task destroy

This is useful for CI pipelines. See the Solo deployment with Hardhat Example for full details.


Troubleshooting

SymptomLikely CauseFix
connection refused on port 37546Relay not runningRun one-shot single deploy or solo relay node add
invalid sender or signature errorUsing ED25519 key instead of ECDSAUse ECDSA keys from accounts.json
Hardhat chainId mismatch errorMissing or wrong chainId in configSet chainId: 298 in hardhat.config.ts
MetaMask shows wrong networkChain ID mismatchEnsure Chain ID is 298 in MetaMask network settings
INSUFFICIENT_TX_FEE on transactionAccount not fundedUse a pre-funded ECDSA account from accounts.json
Hardhat test timeoutNetwork not fully startedWait for one-shot to fully complete before running tests
Port 37546 already in useAnother process is using the portRun lsof -i :37546 and stop the conflicting process

Further Reading

4 - Using Network Load Generator with Solo

Learn how to run load tests against a Solo network using the Network Load Generator (NLG). Generate realistic transaction flows and stress-test your deployment to verify performance under load.

Using Network Load Generator with Solo

The Network Load Generator (NLG) is a benchmarking tool that stress tests Hiero networks by generating configurable transaction loads. Use it to validate the performance and stability of your Solo network before deploying to production or running integration tests.

Prerequisites

Before proceeding, ensure you have completed the following:

  • System Readiness — your local environment meets all hardware and software requirements.
  • Quickstart — you have a running Solo network and are familiar with the basic Solo workflow.

Step 1: Start a Load Test

Use the rapid-fire load start command to install the NLG Helm chart and begin a load test against your deployment.

   npx @hiero-ledger/solo@latest rapid-fire load start \
 --deployment <deployment-name> \
 --args '"-c 3 -a 10 -t 60"' \
 --test CryptoTransferLoadTest

Replace <deployment-name> with your deployment name - find it with solo one-shot show deployment (see Capture your deployment name).

The --args flag passes arguments directly to the NLG. In this example:

  • -c 3 — 3 concurrent threads
  • -a 10 — 10 accounts
  • -t 60 — run for 60 seconds

Step 2: Run Multiple Load Tests (Optional)

You can run additional load tests in parallel from a separate terminal. Each test runs independently against the same deployment:

    npx @hiero-ledger/solo@latest rapid-fire load start \
  --deployment <deployment-name> \
  --args '"-c 3 -a 10 -t 60"' \
  --test NftTransferLoadTest

Step 3: Stop a Specific Load Test

To stop a single running load test before it completes, use the stop command:

    npx @hiero-ledger/solo@latest rapid-fire load stop \
  --deployment <deployment-name> \
  --test CryptoTransferLoadTest

Step 4: Tear Down All Load Tests

To stop all running load tests and uninstall the NLG Helm chart:

    npx @hiero-ledger/solo@latest rapid-fire destroy all \
  --deployment <deployment-name>

Complete Example

For an end-to-end walkthrough with a full configuration, see the examples/rapid-fire.

Available Tests and Arguments

A full list of all available rapid-fire commands can be found in Solo CLI Reference.

5 - Deploying a Local Consensus Node Build

Test unreleased hiero-consensus-node changes end-to-end using Solo’s –local-build-path flag — no Docker image rebuild or registry push required.

Overview

Solo’s --local-build-path flag lets you deploy a network using a consensus node binary you compiled locally. Use this when you need to:

  • Test unreleased hiero-consensus-node code against a live Solo network.
  • Reproduce a bug with a specific build.
  • Iterate on platform changes without a full release cycle.

Solo validates the path for the expected apps/ and lib/ subdirectories, then uses kubectl cp to push the local binaries directly into the running node pods — no Docker image rebuild or registry push required.

Scope: This guide covers the consensus node local build workflow. Local build support for mirror node, block node, relay, and explorer requires additional engineering work and is not yet available as a first-class Solo feature.


Prerequisites

  • Solo CLI installed — if you have not yet deployed a network, follow the Quickstart first.

  • hiero-consensus-node cloned locally — see Step 1.

  • Java 25 (Temurin) — this is a hard Gradle toolchain requirement; Java 21 will fail with a cryptic toolchain error. Install with SDKMAN:

    sdk install java 25.0.2-tem
    
  • Gradle — the repository includes the Gradle wrapper (./gradlew); no separate Gradle install is needed.


Step 1: Build hiero-consensus-node

Clone the repository and run ./gradlew assemble. This compiles the consensus node and populates hedera-node/data/ with the runtime artifacts Solo needs:

  • hedera-node/data/lib/ — runtime dependency JARs
  • hedera-node/data/apps/HederaNode.jar — the main consensus node binary
git clone https://github.com/hiero-ledger/hiero-consensus-node.git
cd hiero-consensus-node
./gradlew assemble

To build a specific release tag, use --branch:

git clone https://github.com/hiero-ledger/hiero-consensus-node.git \
  --depth 1 --branch @<VERSION>
cd hiero-consensus-node
./gradlew assemble

Note: The initial Gradle build downloads dependencies and compiles all modules. Expect 10–30 minutes on a first run; subsequent incremental builds are faster.

Note: Solo copies data/lib/ and data/apps/ from your build path but skips data/config/ and data/keys/ — those come from the container image. To customise consensus node configuration, see Custom Application Properties.


Step 2: Deploy with your local build

Choose the path that matches your situation.

Option A — New cluster (Falcon deploy)

Creates a fresh Kind cluster and deploys the full Solo network from scratch, using your local build for the consensus node.

Create a values file with the --local-build-path flag:

# local-build-values.yaml
setup:
  --local-build-path: "/absolute/path/to/hiero-consensus-node/hedera-node/data"

Then deploy:

solo one-shot falcon deploy --values-file local-build-values.yaml

Use an absolute path — relative paths can behave unexpectedly depending on where Solo is invoked.

For a full deployment with mirror node, explorer, and relay, add the corresponding sections to your values file. See the One-Shot Falcon Deployment guide for the complete values file reference.

Option B — Existing cluster (consensus node setup)

If you already have a running Solo deployment and want to swap in a new consensus node binary without redeploying the whole network:

solo consensus node setup \
  --deployment <deployment-name> \
  --local-build-path /absolute/path/to/hiero-consensus-node/hedera-node/data

Replace <deployment-name> with your deployment name — retrieve it with:

cat ~/.solo/cache/last-one-shot-deployment.txt

Step 3: Verify the local build is running

Confirm that the consensus node gRPC port is reachable:

nc -zv localhost 35211

Expected output:

Connection to localhost port 35211 [tcp/*] succeeded!

Confirm that the pods are running your local build by inspecting the container images:

kubectl get pods -n <namespace> -o \
  jsonpath='{range .items[*]}{.metadata.name}{"\t"}{.spec.containers[*].image}{"\n"}{end}'

Replace <namespace> with your deployment namespace (default: one-shot). The consensus node pod should reference the image tag that matches your --release-tag (or the Solo built-in default if you omitted it). The key indicator is that the node started successfully with your local data/lib/ and data/apps/ artifacts copied in.


Step 4: Tear down

solo one-shot falcon destroy

Reference: ready-to-run example

The Solo repository ships a Task-based example that automates the full workflow — cloning at the correct versions, building the consensus node, generating an absolute-path values file, and deploying:

Run the full workflow with:

task

Troubleshooting

SymptomLikely causeFix
--local-build-path: path does not exist./gradlew assemble has not run, or the path is wrongConfirm: ls /absolute/path/to/hiero-consensus-node/hedera-node/data/apps/HederaNode.jar
./gradlew assemble fails with Unsupported class file major versionWrong Java versionCheck java -version; Java 25 (Temurin) is required. Install: sdk install java 25.0.2-tem
Consensus node pods crash on startBuild artifacts incompatible with the base container imageSet --release-tag in your values file to match the source tag you built from
nc -zv localhost 35211 fails after deployPort-forward diedRestore: kubectl port-forward svc/haproxy-node1-svc -n one-shot 35211:50211 &
Slow first deploymentMirror node, relay, and explorer images pulling for the first timeLet it complete; subsequent runs reuse the image cache