├── .gitignore ├── local ├── haproxy-svc-patch.yaml ├── all-in-setup.sh ├── compose.yaml ├── kind-config.yaml ├── create-ecdsa-account.sh ├── Dockerfile ├── setup-solo-test-net.sh └── README.md ├── extractAccountAsJson.py ├── scripts ├── check-accounts.sh └── check-grpc-connections.sh ├── MAINTAINERS.md ├── README.md ├── .github └── workflows │ └── validation.yml ├── LICENSE └── action.yml /.gitignore: -------------------------------------------------------------------------------- 1 | /.idea/* 2 | -------------------------------------------------------------------------------- /local/haproxy-svc-patch.yaml: -------------------------------------------------------------------------------- 1 | spec: 2 | ports: 3 | - nodePort: 32767 4 | port: 50211 5 | -------------------------------------------------------------------------------- /local/all-in-setup.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | ./setup-solo-test-net.sh 4 | ./create-ecdsa-account.sh 5 | -------------------------------------------------------------------------------- /local/compose.yaml: -------------------------------------------------------------------------------- 1 | services: 2 | solo-test-cluster: 3 | build: 4 | context: . 5 | additional_contexts: 6 | - gh=.. 7 | volumes: 8 | - /var/run/docker.sock:/var/run/docker.sock 9 | network_mode: host 10 | -------------------------------------------------------------------------------- /local/kind-config.yaml: -------------------------------------------------------------------------------- 1 | kind: Cluster 2 | apiVersion: kind.x-k8s.io/v1alpha4 3 | nodes: 4 | - role: control-plane 5 | extraPortMappings: 6 | - containerPort: 32767 7 | hostPort: 50211 8 | listenAddress: "127.0.0.1" 9 | protocol: TCP 10 | -------------------------------------------------------------------------------- /extractAccountAsJson.py: -------------------------------------------------------------------------------- 1 | import re 2 | import sys 3 | import os 4 | input_text = sys.stdin.read() 5 | json_regex = r'\{\s*\"accountId\":\s*\".*?\",\s*\"publicKey\":\s*\".*?\",\s*\"balance\":\s*\d+\s*\}' 6 | json_match = re.search(json_regex, input_text) 7 | 8 | if json_match: 9 | json_block = json_match.group(0) 10 | print(json_block) -------------------------------------------------------------------------------- /local/create-ecdsa-account.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | echo "Creating ECDSA account..." 4 | solo ledger account create --generate-ecdsa-key --deployment $SOLO_DEPLOYMENT > account_create_output_ecdsa.txt 5 | cat account_create_output_ecdsa.txt 6 | JSON=$(cat account_create_output_ecdsa.txt | python3 ./extractAccountAsJson.py) || { 7 | echo "Error: Python script extractAccountAsJson.py failed" 8 | exit 1 9 | } 10 | export ACCOUNT_ID=$(echo $JSON | jq -r '.accountId') 11 | export ACCOUNT_PUBLIC_KEY=$(echo $JSON | jq -r '.publicKey') 12 | export ACCOUNT_PRIVATE_KEY=$(kubectl get secret account-key-$ACCOUNT_ID -n $SOLO_NAMESPACE -o jsonpath='{.data.privateKey}' | base64 -d | xargs) 13 | solo ledger account update --account-id $ACCOUNT_ID --hbar-amount 10000000 --deployment $SOLO_DEPLOYMENT 14 | echo "accountId=$ACCOUNT_ID" 15 | echo "publicKey=$ACCOUNT_PUBLIC_KEY" 16 | echo "privateKey=$ACCOUNT_PRIVATE_KEY" 17 | -------------------------------------------------------------------------------- /scripts/check-accounts.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | # Validate Generic Outputs (ED25519-based) 6 | if [ -z "$ACCOUNT_ID" ]; then 7 | echo "❌ Error: Generic accountId (ED25519) is missing!" 8 | exit 1 9 | fi 10 | if [ -z "$PRIVATE_KEY" ]; then 11 | echo "❌ Error: Generic privateKey (ED25519) is missing!" 12 | exit 1 13 | fi 14 | if [ -z "$PUBLIC_KEY" ]; then 15 | echo "❌ Error: Generic publicKey (ED25519) is missing!" 16 | exit 1 17 | fi 18 | 19 | # Validate ECDSA Outputs 20 | if [ -z "$ECDSA_ACCOUNT_ID" ]; then 21 | echo "❌ Error: ECDSA accountId is missing!" 22 | exit 1 23 | fi 24 | if [ -z "$ECDSA_PRIVATE_KEY" ]; then 25 | echo "❌ Error: ECDSA privateKey is missing!" 26 | exit 1 27 | fi 28 | if [ -z "$ECDSA_PUBLIC_KEY" ]; then 29 | echo "❌ Error: ECDSA publicKey is missing!" 30 | exit 1 31 | fi 32 | 33 | # Validate ED25519 Outputs 34 | if [ -z "$ED25519_ACCOUNT_ID" ]; then 35 | echo "❌ Error: ED25519 accountId is missing!" 36 | exit 1 37 | fi 38 | if [ -z "$ED25519_PRIVATE_KEY" ]; then 39 | echo "❌ Error: ED25519 privateKey is missing!" 40 | exit 1 41 | fi 42 | if [ -z "$ED25519_PUBLIC_KEY" ]; then 43 | echo "❌ Error: ED25519 publicKey is missing!" 44 | exit 1 45 | fi 46 | 47 | echo "🎉 All outputs are valid!" 48 | -------------------------------------------------------------------------------- /local/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM busybox:1.37.0 AS installer 2 | ARG KIND_VERSION=0.29.0 3 | RUN wget -O ./kind https://kind.sigs.k8s.io/dl/v${KIND_VERSION}/kind-linux-amd64 4 | RUN chmod +x ./kind 5 | 6 | ARG DOCKER_VERSION=28.3.2 7 | RUN wget -O ./docker.tgz https://download.docker.com/linux/static/stable/x86_64/docker-${DOCKER_VERSION}.tgz 8 | RUN tar xzvf ./docker.tgz 9 | 10 | ARG KUBECTL_VERSION=1.33.3 11 | RUN wget -O ./kubectl "https://dl.k8s.io/release/v${KUBECTL_VERSION}/bin/linux/amd64/kubectl" 12 | RUN chmod +x ./kubectl 13 | 14 | ARG HELM_VERSION=3.17.4 15 | RUN wget -O ./helm.tgz https://get.helm.sh/helm-v${HELM_VERSION}-linux-amd64.tar.gz 16 | RUN tar xzvf ./helm.tgz 17 | 18 | FROM node:22-alpine 19 | COPY --from=installer ./kind /usr/local/bin/kind 20 | COPY --from=installer ./docker/docker /usr/local/bin/docker 21 | COPY --from=installer ./kubectl /usr/local/bin/kubectl 22 | COPY --from=installer ./linux-amd64 /usr/local/bin/helm 23 | 24 | RUN npm install -g @hashgraph/solo@0.39.0 25 | RUN apk add --update --no-cache python3 jq 26 | 27 | COPY ./setup-solo-test-net.sh ./setup-solo-test-net.sh 28 | COPY ./kind-config.yaml ./kind-config.yaml 29 | COPY ./haproxy-svc-patch.yaml ./haproxy-svc-patch.yaml 30 | RUN chmod +x ./setup-solo-test-net.sh 31 | 32 | COPY ./create-ecdsa-account.sh ./create-ecdsa-account.sh 33 | COPY --from=gh ./extractAccountAsJson.py ./extractAccountAsJson.py 34 | RUN chmod +x ./create-ecdsa-account.sh 35 | 36 | COPY ./all-in-setup.sh ./all-in-setup.sh 37 | RUN chmod +x ./all-in-setup.sh 38 | 39 | 40 | ENV SOLO_CLUSTER_NAME=solo-e2e 41 | ENV SOLO_NAMESPACE=solo 42 | ENV SOLO_CLUSTER_SETUP_NAMESPACE=solo-cluster 43 | ENV SOLO_DEPLOYMENT=solo-deployment 44 | ARG HIERO_VERSION=v0.63.7 45 | ENTRYPOINT [ "./all-in-setup.sh"] -------------------------------------------------------------------------------- /local/setup-solo-test-net.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | # Create a Kubernetes cluster using kind 3 | kind create cluster -n $SOLO_CLUSTER_NAME --config=./kind-config.yaml 4 | 5 | # Create kubectl config file 6 | kind get kubeconfig --name solo-e2e > ~/.kube/config 7 | 8 | # Initialize the Solo CLI configuration 9 | solo init 10 | 11 | # Connect the Solo CLI to the kind cluster using a cluster reference name 12 | solo cluster-ref connect --cluster-ref kind-$SOLO_CLUSTER_NAME --context kind-$SOLO_CLUSTER_NAME 13 | 14 | # Create deployment 15 | solo deployment create -n $SOLO_NAMESPACE --deployment $SOLO_DEPLOYMENT 16 | 17 | # Add the kind cluster to the deployment with 1 consensus node 18 | solo deployment add-cluster --deployment $SOLO_DEPLOYMENT --cluster-ref kind-$SOLO_CLUSTER_NAME --num-consensus-nodes 1 19 | 20 | # Generate node keys 21 | solo node keys --gossip-keys --tls-keys -i node1 --deployment $SOLO_DEPLOYMENT 22 | 23 | # Setup the Solo cluster 24 | solo cluster-ref setup -s $SOLO_CLUSTER_NAME 25 | 26 | # Deploy network 27 | solo network deploy -i node1 --deployment $SOLO_DEPLOYMENT 28 | 29 | # Setup node 30 | solo node setup -i node1 --deployment $SOLO_DEPLOYMENT -t $HIERO_VERSION --quiet-mode 31 | 32 | # Start node 33 | solo node start -i node1 --deployment $SOLO_DEPLOYMENT 34 | 35 | # Debug: List services in the solo namespace 36 | echo "Listing services in namespace $SOLO_NAMESPACE:" 37 | kubectl get svc -n $SOLO_NAMESPACE 38 | 39 | # Port forward HAProxy (only if service exists) 40 | if kubectl get svc haproxy-node1-svc -n $SOLO_NAMESPACE >/dev/null 2>&1; then 41 | kubectl patch svc haproxy-node1-svc -n $SOLO_NAMESPACE --patch-file haproxy-svc-patch.yaml 42 | echo "HAProxy service haproxy-node1-svc is available under localhost:50211" 43 | else 44 | echo "HAProxy service haproxy-node1-svc not found, skipping port-forward" 45 | fi 46 | -------------------------------------------------------------------------------- /MAINTAINERS.md: -------------------------------------------------------------------------------- 1 | # Maintainers 2 | 3 | The general handling of Maintainer rights and all groups in this GitHub org is done in the https://github.com/hiero-ledger/governance repository. 4 | 5 | ## Maintainer Scopes, GitHub Roles and GitHub Teams 6 | 7 | Maintainers are assigned the following scopes in this repository: 8 | 9 | | Scope | Definition | GitHub Role | GitHub Team | 10 | |---------------------|-----------------------------------|-------------|----------------------------------| 11 | | project-maintainers | The Maintainers of the project | Maintain | `hiero-solo-action-maintainers` | 12 | | tsc | The Hiero TSC | Maintain | `tsc` | 13 | | github-maintainers | The Maintainers of the github org | Maintain | `github-maintainers` | 14 | 15 | ## Active Maintainers 16 | 17 | 18 | 19 | | Name | GitHub ID | Scope | LFID | Discord ID | Email | Company Affiliation | 20 | |--------------- | ------------- | ----- | ---- | -------------- | ----- | ------------------- | 21 | | Hendrik Ebbers | hendrikebbers | | | hendrik.ebbers | | Hashgraph | 22 | | Georgi Stoykov | gsstoykov | | | glime_39042 | | LimeChain | 23 | 24 | 25 | ## Emeritus Maintainers 26 | 27 | | Name | GitHub ID | Scope | LFID | Discord ID | Email | Company Affiliation | 28 | |----- | --------- | ----- | ---- | ---------- | ----- | ------------------- | 29 | | | | | | | | | 30 | 31 | ## The Duties of a Maintainer 32 | 33 | Maintainers are expected to perform duties in alignment with **[Hiero-Ledger's defined maintainer guidelines](https://github.com/hiero-ledger/governance/blob/main/roles-and-groups.md#maintainers).** 34 | -------------------------------------------------------------------------------- /local/README.md: -------------------------------------------------------------------------------- 1 | # Local Solo Test Network 2 | 3 | ## How to set up a local Solo Test Network 4 | This folder contains a `Dockerfile` for a Docker container that prepares a K8s cluster with a solo test network without installing additional tools except of Docker. 5 | 6 | This Docker image contains tools like: 7 | 8 | - helm 9 | - kind 10 | - solo 11 | - kubectl 12 | 13 | You have two options to run a local solo test network: 14 | 15 | 2. With plain Docker or 16 | 1. with Docker Compose 17 | 18 | 19 | ### Plain Docker 20 | 21 | First at all, you have to build the Docker image: 22 | 23 | ```shell 24 | docker build -t hiero/solo-runner . --build-context gh=.. 25 | ``` 26 | 27 | The flag `--build-context` is important to copy files from the GH action 28 | 29 | The versions and some settings of the contained tools can be overriden during the build: 30 | 31 | ```shell 32 | docker build --build-args HELM_VERSION ... -t hiero/solo-runner . 33 | ``` 34 | 35 | An overview of all args variable: 36 | 37 | | Variable Name | Default |Description | 38 | |----------------|---------|-------------------------------| 39 | | DOCKER_VERSION | 28.3.2 | Version of the used `docker` | 40 | | HELM_VERSION | 3.17.4 | Version of the used `helm` | 41 | | HIERO_VERSION | v0.63.7 | Version of the used Hiero consensus node | 42 | | KIND_VERSION | 0.29.0 | Version of the used `kind` (Note: It is not the version of the KIND node ) | 43 | | KUBECTL_VERSION |1.33.3 | Version of the used `kubectl` | 44 | 45 | After building the Docker image, we can run it to set up a local Solo Test Network: 46 | 47 | ```shell 48 | docker run -v /var/run/docker.sock:/var/run/docker.sock --network host -it hiero/solo-runner 49 | ``` 50 | 51 | Both flags (`-v /var/run/docker.sock:/var/run/docker.sock --network host`) are important to start the solo test network in a K8s cluster directly on your host machine. 52 | 53 | 54 | ### Docker Compose 55 | If you don't want to care about building a Docker image, we prepare a `compose.yaml` that automatically build and run the container, that is defined in `Dockerfile`. 56 | 57 | ```shell 58 | docker compose up --build 59 | ``` 60 | 61 | The flag `--build` is optional. 62 | It forces to build the Docker image at every run. 63 | 64 | 65 | ## Interaction with the Local Solo Test Network 66 | 67 | ### Access to HAProxy 68 | You can interact with the internal HAProxy via `localhost:50211`from your host machine. 69 | -------------------------------------------------------------------------------- /scripts/check-grpc-connections.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | echo "🔍 Validating gRPC connections..." 6 | 7 | # Wait a bit for port forwarding to establish 8 | echo "⏳ Waiting for port forwarding to establish..." 9 | sleep 10 10 | 11 | # Debug: Show what's listening on the expected ports 12 | echo "🔍 Debug: Checking what's listening on expected ports..." 13 | netstat -tlnp 2>/dev/null | grep -E ":(9998|5600|50211)" || echo "No services found on expected ports yet" 14 | 15 | # Function to check if a port is listening 16 | check_port() { 17 | local port=$1 18 | local service_name=$2 19 | local timeout=30 20 | local count=0 21 | 22 | echo "Checking $service_name on port $port..." 23 | 24 | while [ $count -lt $timeout ]; do 25 | if nc -z localhost $port 2>/dev/null; then 26 | echo "✅ $service_name is listening on port $port" 27 | return 0 28 | fi 29 | echo "⏳ Waiting for $service_name on port $port... ($((count + 1))/$timeout)" 30 | sleep 1 31 | count=$((count + 1)) 32 | done 33 | 34 | echo "❌ Timeout: $service_name is not listening on port $port after $timeout seconds" 35 | return 1 36 | } 37 | 38 | # Function to test gRPC connection using grpcurl (if available) 39 | test_grpc_connection() { 40 | local port=$1 41 | local service_name=$2 42 | 43 | # Check if grpcurl is available 44 | if command -v grpcurl >/dev/null 2>&1; then 45 | echo "Testing gRPC connection to $service_name on port $port..." 46 | echo "grpcurl version: $(grpcurl --version)" 47 | 48 | # Only test gRPC reflection for Mirror Node gRPC (port 5600) which supports it 49 | if [ "$port" = "5600" ]; then 50 | # Try to list services (this is a common gRPC reflection endpoint) 51 | if timeout 10 grpcurl -plaintext localhost:$port list >/dev/null 2>&1; then 52 | echo "✅ gRPC connection to $service_name successful" 53 | echo "Available services:" 54 | timeout 10 grpcurl -plaintext localhost:$port list 55 | return 0 56 | else 57 | echo "⚠️ gRPC reflection not available, but port is listening" 58 | return 0 59 | fi 60 | else 61 | # For other ports (9998, 50211), just confirm the port is listening 62 | return 0 63 | fi 64 | else 65 | echo "❌ grpcurl not available - checking installation..." 66 | echo "PATH: $PATH" 67 | echo "which grpcurl: $(which grpcurl 2>/dev/null || echo 'not found')" 68 | echo "ls /usr/local/bin/grpcurl: $(ls -la /usr/local/bin/grpcurl 2>/dev/null || echo 'not found')" 69 | return 0 70 | fi 71 | } 72 | 73 | # Check gRPC Proxy (always available) 74 | if check_port 9998 "gRPC Proxy"; then 75 | test_grpc_connection 9998 "gRPC Proxy" 76 | else 77 | echo "❌ gRPC Proxy validation failed" 78 | exit 1 79 | fi 80 | 81 | # Check HAProxy (always available) 82 | if check_port 50211 "HAProxy"; then 83 | test_grpc_connection 50211 "HAProxy" 84 | else 85 | echo "❌ HAProxy validation failed" 86 | exit 1 87 | fi 88 | 89 | # Check Mirror Node gRPC (only if mirror node is installed) 90 | if [ "${INSTALL_MIRROR_NODE}" = "true" ]; then 91 | if check_port 5600 "Mirror Node gRPC"; then 92 | test_grpc_connection 5600 "Mirror Node gRPC" 93 | else 94 | echo "❌ Mirror Node gRPC validation failed" 95 | exit 1 96 | fi 97 | else 98 | echo "ℹ️ Mirror Node not installed, skipping Mirror Node gRPC validation" 99 | fi 100 | 101 | echo "🎉 All gRPC connections validated successfully!" 102 | 103 | # Summary 104 | echo "" 105 | echo "📋 Validation Summary:" 106 | echo "✅ gRPC Proxy (port 9998): Validated" 107 | echo "✅ HAProxy (port 50211): Validated" 108 | if [ "${INSTALL_MIRROR_NODE}" = "true" ]; then 109 | echo "✅ Mirror Node gRPC (port 5600): Validated" 110 | else 111 | echo "ℹ️ Mirror Node gRPC (port 5600): Not installed" 112 | fi 113 | 114 | # Final debug: Show all listening ports 115 | echo "🔍 Final debug: All listening ports:" 116 | netstat -tlnp 2>/dev/null | grep LISTEN || echo "No listening ports found" 117 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![OpenSSF Scorecard](https://api.scorecard.dev/projects/github.com/hiero-ledger/hiero-solo-action/badge)](https://scorecard.dev/viewer/?uri=github.com/hiero-ledger/hiero-solo-action) 2 | [![CII Best Practices](https://bestpractices.coreinfrastructure.org/projects/10697/badge)](https://bestpractices.coreinfrastructure.org/projects/10697) 3 | [![License](https://img.shields.io/badge/license-apache2-blue.svg)](LICENSE) 4 | 5 | # hiero-solo-action 6 | 7 | A GitHub Action for setting up a Hiero Solo network. 8 | An overview of the usage and idea of the action can be found [here](https://dev.to/hendrikebbers/ci-for-hedera-based-projects-2nja). 9 | 10 | The network that is created by the action contains one consensus node that can be accessed at `localhost:50211`. 11 | Optionally, you can deploy a second consensus node by enabling `dualMode: true`. When dual mode is enabled, the second node is accessible at `localhost:51211`. 12 | When a mirror node is installed, the Java-based REST API can be accessed at `localhost:8084`. 13 | The action creates an account on the network that contains 10,000,000 hbars. 14 | All information about the account is stored as output to the github action. 15 | 16 | A good example on how the action is used can be found at the [hiero-enterprise project action](<[https://github.com/OpenElements/hedera-enterprise/blob/main/.github/workflows/maven.yml](https://github.com/OpenElements/hiero-enterprise-java/blob/main/.github/workflows/maven.yml)>). Here the action is used to create a temporary network that is than used to execute tests against the network. 17 | 18 | ## Inputs 19 | 20 | The GitHub action takes the following inputs: 21 | 22 | | Input | Required | Default | Description | 23 | | ------------------------ | -------- | ---------- | ------------------------------------------------------------------------------------------------------------------------------------------ | 24 | | `hbarAmount` | false | `10000000` | Amount of hbars to fund a created account with. | 25 | | `hieroVersion` | false | `v0.58.10` | Hiero consenus node version to use | 26 | | `mirrorNodeVersion` | false | `v0.133.0` | Mirror node version to use | 27 | | `installMirrorNode` | false | `false` | If set to `true`, the action will install a mirror node in addition to the main node. The mirror node can be accessed at `localhost:5551`. | 28 | | `mirrorNodePortRest` | false | `5551` | Port for Mirror Node REST API | 29 | | `mirrorNodePortGrpc` | false | `5600` | Port for Mirror Node gRPC | 30 | | `mirrorNodePortWeb3Rest` | false | `8545` | Port for Web3 REST API | 31 | | `installRelay` | false | `false` | If set to `true`, the action will install the JSON-RPC-Relay as part of the setup process. | 32 | | `relayPort` | false | `7546` | Port for the JSON-RPC-Relay | 33 | | `grpcProxyPort` | false | `9998` | Port for gRPC Proxy | 34 | | `dualModeGrpcProxyPort` | false | `9999` | Port for the gRPC Proxy of the second consensus node (only if dual mode is enabled) | 35 | | `haproxyPort` | false | `50211` | Port for HAProxy | 36 | | `soloVersion` | false | `0.41.0` | Version of Solo CLI to install | 37 | | `javaRestApiPort` | false | `8084` | Port for Java-based REST API | 38 | | `dualMode` | false | `false` | Enable dual mode to deploy two consensus nodes | 39 | 40 | > [! IMPORTANT] 41 | > The used Solo version isn't compatible with Hiero consenus node versions above v0.58.10. 42 | > Therefore we recommend to not change `hieroVersion`. 43 | 44 | ## Outputs 45 | 46 | | Output | Description | 47 | | -------------------------------------- | ---------------------------------------------------------- | 48 | | `steps.solo.outputs.accountId` | The account ID of account created in ED25519 format. | 49 | | `steps.solo.outputs.publicKey` | The public key of account created in ED25519 format. | 50 | | `steps.solo.outputs.privateKey` | The private key of account created in ED25519 format. | 51 | | `steps.solo.outputs.deployment` | The name of the Solo deployment created by the action. | 52 | | `steps.solo.outputs.ecdsaAccountId` | The account ID of the account created (in ECDSA format). | 53 | | `steps.solo.outputs.ecdsaPublicKey` | The public key of the account created (in ECDSA format). | 54 | | `steps.solo.outputs.ecdsaPrivateKey` | The private key of the account created (in ECDSA format). | 55 | | `steps.solo.outputs.ed25519AccountId` | Same as `accountId`, but with an explicit ED25519 format! | 56 | | `steps.solo.outputs.ed25519PublicKey` | Same as `publicKey`, but with an explicit ED25519 format! | 57 | | `steps.solo.outputs.ed25519PrivateKey` | Same as `privateKey`, but with an explicit ED25519 format! | 58 | 59 | # Simple usage 60 | 61 | ```yaml 62 | - name: Setup Hiero Solo 63 | uses: hiero-ledger/hiero-solo-action@v0.8 64 | id: solo 65 | 66 | - name: Use Hiero Solo 67 | run: | 68 | echo "Account ID: ${{ steps.solo.outputs.accountId }}" 69 | echo "Private Key: ${{ steps.solo.outputs.privateKey }}" 70 | echo "Public Key: ${{ steps.solo.outputs.publicKey }}" 71 | ``` 72 | 73 | # Usage with `ecdsa` account format 74 | 75 | ```yaml 76 | - name: Setup Hiero Solo 77 | uses: hiero-ledger/hiero-solo-action@v0.8 78 | id: solo 79 | 80 | - name: Use Hiero Solo 81 | run: | 82 | echo "Account ID: ${{ steps.solo.outputs.ecdsaAccountId }}" 83 | echo "Private Key: ${{ steps.solo.outputs.ecdsaPrivateKey }}" 84 | echo "Public Key: ${{ steps.solo.outputs.ecdsaPublicKey }}" 85 | ``` 86 | 87 | # Usage with `ED25519` account format 88 | 89 | ```yaml 90 | - name: Setup Hiero Solo 91 | uses: hiero-ledger/hiero-solo-action@v0.8 92 | id: solo 93 | 94 | - name: Use Hiero Solo 95 | run: | 96 | echo "Account ID: ${{ steps.solo.outputs.ed25519AccountId }}" 97 | echo "Private Key: ${{ steps.solo.outputs.ed25519PrivateKey }}" 98 | echo "Public Key: ${{ steps.solo.outputs.ed25519PublicKey }}" 99 | ``` 100 | 101 | # Usage with `hbarAmount` 102 | 103 | ```yaml 104 | - name: Setup Hiero Solo 105 | uses: hiero-ledger/hiero-solo-action@v0.8 106 | id: solo 107 | with: 108 | hbarAmount: 10000000 109 | 110 | - name: Use Hiero Solo 111 | run: | 112 | echo "Account ID: ${{ steps.solo.outputs.accountId }}" 113 | # Display account information including the current amount of HBAR 114 | solo account get --account-id ${{ steps.solo.outputs.accountId }} --deployment "solo-deployment" 115 | ``` 116 | 117 | # Usage with Dual Mode (2 Consensus Nodes) 118 | 119 | ```yaml 120 | - name: Setup Hiero Solo with Dual Mode 121 | uses: hiero-ledger/hiero-solo-action@v0.8 122 | id: solo 123 | with: 124 | dualMode: true 125 | 126 | - name: Verify Nodes 127 | run: | 128 | echo "Checking services for both nodes..." 129 | kubectl get svc -n solo 130 | kubectl get pods -n solo 131 | echo "Node 1 is accessible at localhost:50211" 132 | echo "Node 2 is accessible at localhost:51211" 133 | echo "Account ID: ${{ steps.solo.outputs.accountId }}" 134 | ``` 135 | 136 | # Local Solo Test Network 137 | 138 | The [README.md](./local/README.md) describes how to set up a local solo test network only with Docker. 139 | 140 | # Tributes 141 | 142 | This action is based on the work of [Hiero Solo](https://github.com/hiero-ledger/solo). 143 | Without the great help of [Timo](https://github.com/timo0), [Nathan](https://github.com/nathanklick), and [Lenin](https://github.com/leninmehedy) this action would not exist. 144 | -------------------------------------------------------------------------------- /.github/workflows/validation.yml: -------------------------------------------------------------------------------- 1 | name: PR Validation 2 | permissions: 3 | contents: read 4 | 5 | on: 6 | pull_request: 7 | branches: 8 | - main 9 | 10 | jobs: 11 | validate-outputs: 12 | runs-on: ubuntu-latest 13 | steps: 14 | - name: Harden the runner (Audit all outbound calls) 15 | uses: step-security/harden-runner@0634a2670c59f64b4a01f0f96f84700a4088b9f0 # v2.12.0 16 | with: 17 | egress-policy: audit 18 | 19 | - name: 🚀 Checkout Repo 20 | uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 21 | 22 | - name: ⚙️ Setup Hiero Solo 23 | id: solo 24 | uses: ./ 25 | 26 | - name: ✅ Validate Hiero Solo Outputs 27 | env: 28 | ACCOUNT_ID: ${{ steps.solo.outputs.accountId }} 29 | PRIVATE_KEY: ${{ steps.solo.outputs.privateKey }} 30 | PUBLIC_KEY: ${{ steps.solo.outputs.publicKey }} 31 | ECDSA_ACCOUNT_ID: ${{ steps.solo.outputs.ecdsaAccountId }} 32 | ECDSA_PRIVATE_KEY: ${{ steps.solo.outputs.ecdsaPrivateKey }} 33 | ECDSA_PUBLIC_KEY: ${{ steps.solo.outputs.ecdsaPublicKey }} 34 | ED25519_ACCOUNT_ID: ${{ steps.solo.outputs.ed25519AccountId }} 35 | ED25519_PRIVATE_KEY: ${{ steps.solo.outputs.ed25519PrivateKey }} 36 | ED25519_PUBLIC_KEY: ${{ steps.solo.outputs.ed25519PublicKey }} 37 | run: ${GITHUB_WORKSPACE}/scripts/check-accounts.sh 38 | 39 | validate-outputs-pre-0440: 40 | runs-on: ubuntu-latest 41 | steps: 42 | - name: Harden the runner (Audit all outbound calls) 43 | uses: step-security/harden-runner@0634a2670c59f64b4a01f0f96f84700a4088b9f0 # v2.12.0 44 | with: 45 | egress-policy: audit 46 | 47 | - name: 🚀 Checkout Repo 48 | uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 49 | 50 | - name: ⚙️ Setup Hiero Solo 51 | id: solo 52 | uses: ./ 53 | with: 54 | soloVersion: '0.43.2' 55 | 56 | - name: ✅ Validate Hiero Solo Outputs 57 | env: 58 | ACCOUNT_ID: ${{ steps.solo.outputs.accountId }} 59 | PRIVATE_KEY: ${{ steps.solo.outputs.privateKey }} 60 | PUBLIC_KEY: ${{ steps.solo.outputs.publicKey }} 61 | ECDSA_ACCOUNT_ID: ${{ steps.solo.outputs.ecdsaAccountId }} 62 | ECDSA_PRIVATE_KEY: ${{ steps.solo.outputs.ecdsaPrivateKey }} 63 | ECDSA_PUBLIC_KEY: ${{ steps.solo.outputs.ecdsaPublicKey }} 64 | ED25519_ACCOUNT_ID: ${{ steps.solo.outputs.ed25519AccountId }} 65 | ED25519_PRIVATE_KEY: ${{ steps.solo.outputs.ed25519PrivateKey }} 66 | ED25519_PUBLIC_KEY: ${{ steps.solo.outputs.ed25519PublicKey }} 67 | run: ${GITHUB_WORKSPACE}/scripts/check-accounts.sh 68 | 69 | validate-hbar-amount: 70 | runs-on: ubuntu-latest 71 | steps: 72 | - name: Harden the runner (Audit all outbound calls) 73 | uses: step-security/harden-runner@0634a2670c59f64b4a01f0f96f84700a4088b9f0 # v2.12.0 74 | with: 75 | egress-policy: audit 76 | 77 | - name: 🚀 Checkout Repo 78 | uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 79 | 80 | - name: ⚙️ Setup Hiero Solo 81 | id: solo 82 | uses: ./ 83 | with: 84 | hbarAmount: '100000001' 85 | 86 | - name: ✅ Validate Solo Account HBAR Amount 87 | env: 88 | ACCOUNT_ID: ${{ steps.solo.outputs.accountId }} 89 | run: solo ledger account info --account-id "${ACCOUNT_ID}" --deployment "solo-deployment" 90 | 91 | validate-mirror-node: 92 | runs-on: ubuntu-latest 93 | steps: 94 | - name: Harden the runner (Audit all outbound calls) 95 | uses: step-security/harden-runner@0634a2670c59f64b4a01f0f96f84700a4088b9f0 # v2.12.0 96 | with: 97 | egress-policy: audit 98 | 99 | - name: 🚀 Checkout Repo 100 | uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 101 | 102 | - name: ⚙️ Setup Hiero Solo with MirrorNode 103 | id: solo 104 | uses: ./ 105 | with: 106 | installMirrorNode: true 107 | 108 | - name: ✅ Validate Mirror Node API 109 | run: | 110 | # Log Account details for Generic (ED25519), ECDSA, and ED25519 111 | echo "Generic Account ID (ED25519): ${{ steps.solo.outputs.accountId }}" 112 | echo "Generic Private Key (ED25519): ${{ steps.solo.outputs.privateKey }}" 113 | echo "Generic Public Key (ED25519): ${{ steps.solo.outputs.publicKey }}" 114 | echo "ECDSA Account ID: ${{ steps.solo.outputs.ecdsaAccountId }}" 115 | echo "ECDSA Private Key: ${{ steps.solo.outputs.ecdsaPrivateKey }}" 116 | echo "ECDSA Public Key: ${{ steps.solo.outputs.ecdsaPublicKey }}" 117 | 118 | echo "ED25519 Account ID: ${{ steps.solo.outputs.ed25519AccountId }}" 119 | echo "ED25519 Private Key: ${{ steps.solo.outputs.ed25519PrivateKey }}" 120 | echo "ED25519 Public Key: ${{ steps.solo.outputs.ed25519PublicKey }}" 121 | 122 | # Wait for the mirror node to be fully up and running 123 | sleep 10 124 | 125 | RESPONSE=$(curl -s -w "%{http_code}" -X 'GET' 'http://localhost:5551/api/v1/network/nodes' -H 'accept: application/json' -o response.json) 126 | 127 | if [ "$RESPONSE" -ne 200 ]; then 128 | echo "❌ Error: API request failed with status code $RESPONSE" 129 | cat response.json 130 | exit 1 131 | else 132 | echo "✅ API request successful" 133 | cat response.json 134 | fi 135 | 136 | validate-json-rpc-relay: 137 | runs-on: ubuntu-latest 138 | steps: 139 | - name: Harden the runner (Audit all outbound calls) 140 | uses: step-security/harden-runner@0634a2670c59f64b4a01f0f96f84700a4088b9f0 # v2.12.0 141 | with: 142 | egress-policy: audit 143 | 144 | - name: 🚀 Checkout Repo 145 | uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 146 | 147 | - name: ⚙️ Setup Hiero Solo with JSON-RPC Relay 148 | id: solo 149 | uses: ./ 150 | with: 151 | installRelay: true 152 | 153 | - name: ✅ Validate JSON-RPC Relay 154 | env: 155 | ACCOUNT_ID: ${{ steps.solo.outputs.accountId }} 156 | PRIVATE_KEY: ${{ steps.solo.outputs.privateKey }} 157 | PUBLIC_KEY: ${{ steps.solo.outputs.publicKey }} 158 | ECDSA_ACCOUNT_ID: ${{ steps.solo.outputs.ecdsaAccountId }} 159 | ECDSA_PRIVATE_KEY: ${{ steps.solo.outputs.ecdsaPrivateKey }} 160 | ECDSA_PUBLIC_KEY: ${{ steps.solo.outputs.ecdsaPublicKey }} 161 | ED25519_ACCOUNT_ID: ${{ steps.solo.outputs.ed25519AccountId }} 162 | ED25519_PRIVATE_KEY: ${{ steps.solo.outputs.ed25519PrivateKey }} 163 | ED25519_PUBLIC_KEY: ${{ steps.solo.outputs.ed25519PublicKey }} 164 | run: | 165 | ${GITHUB_WORKSPACE}/scripts/check-accounts.sh 166 | 167 | # Log Account details for Generic (ED25519), ECDSA, and ED25519 168 | echo "Generic Account ID (ED25519): ${{ steps.solo.outputs.accountId }}" 169 | echo "Generic Private Key (ED25519): ${{ steps.solo.outputs.privateKey }}" 170 | echo "Generic Public Key (ED25519): ${{ steps.solo.outputs.publicKey }}" 171 | echo "ECDSA Account ID: ${{ steps.solo.outputs.ecdsaAccountId }}" 172 | echo "ECDSA Private Key: ${{ steps.solo.outputs.ecdsaPrivateKey }}" 173 | echo "ECDSA Public Key: ${{ steps.solo.outputs.ecdsaPublicKey }}" 174 | 175 | echo "ED25519 Account ID: ${{ steps.solo.outputs.ed25519AccountId }}" 176 | echo "ED25519 Private Key: ${{ steps.solo.outputs.ed25519PrivateKey }}" 177 | echo "ED25519 Public Key: ${{ steps.solo.outputs.ed25519PublicKey }}" 178 | 179 | sleep 30 180 | RESPONSE=$(curl -s -w "%{http_code}" -X POST 'http://localhost:7546' \ 181 | -H 'Content-Type: application/json' \ 182 | -d '{"jsonrpc":"2.0","method":"eth_chainId","params":[],"id":1}' \ 183 | -o response.json) 184 | if [ "$RESPONSE" -ne 200 ]; then 185 | echo "❌ Error: JSON-RPC request failed with status code $RESPONSE" 186 | echo "Response body:" 187 | cat response.json 188 | echo "Checking if relay service is running:" 189 | kubectl get pods -n solo | grep relay 190 | exit 1 191 | fi 192 | CHAIN_ID=$(jq -r '.result' response.json) 193 | if [ -z "$CHAIN_ID" ] || [ "$CHAIN_ID" = "null" ]; then 194 | echo "❌ Error: Invalid or missing chainId in response" 195 | echo "Response body:" 196 | cat response.json 197 | exit 1 198 | fi 199 | echo "✅ JSON-RPC Relay request successful" 200 | echo "Chain ID: $CHAIN_ID" 201 | cat response.json 202 | 203 | validate-grpc-connections: 204 | runs-on: ubuntu-latest 205 | steps: 206 | - name: Harden the runner (Audit all outbound calls) 207 | uses: step-security/harden-runner@0634a2670c59f64b4a01f0f96f84700a4088b9f0 # v2.12.0 208 | with: 209 | egress-policy: audit 210 | 211 | - name: 🚀 Checkout Repo 212 | uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 213 | 214 | - name: ⚙️ Setup Hiero Solo 215 | id: solo 216 | uses: ./ 217 | 218 | - name: Install grpcurl 219 | run: | 220 | curl -sSL https://github.com/fullstorydev/grpcurl/releases/download/v1.8.7/grpcurl_1.8.7_linux_x86_64.tar.gz | sudo tar -xz -C /usr/local/bin grpcurl 221 | sudo chmod +x /usr/local/bin/grpcurl 222 | echo "grpcurl installed at: $(which grpcurl)" 223 | echo "grpcurl version: $(grpcurl --version)" 224 | 225 | - name: ✅ Validate gRPC Connections 226 | env: 227 | INSTALL_MIRROR_NODE: false 228 | run: | 229 | chmod +x ${GITHUB_WORKSPACE}/scripts/check-grpc-connections.sh 230 | bash ${GITHUB_WORKSPACE}/scripts/check-grpc-connections.sh 231 | 232 | validate-grpc-connections-with-mirror: 233 | runs-on: ubuntu-latest 234 | steps: 235 | - name: Harden the runner (Audit all outbound calls) 236 | uses: step-security/harden-runner@0634a2670c59f64b4a01f0f96f84700a4088b9f0 # v2.12.0 237 | with: 238 | egress-policy: audit 239 | 240 | - name: 🚀 Checkout Repo 241 | uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 242 | 243 | - name: ⚙️ Setup Hiero Solo with Mirror Node 244 | id: solo 245 | uses: ./ 246 | with: 247 | installMirrorNode: true 248 | 249 | - name: Install grpcurl 250 | run: | 251 | curl -sSL https://github.com/fullstorydev/grpcurl/releases/download/v1.8.7/grpcurl_1.8.7_linux_x86_64.tar.gz | sudo tar -xz -C /usr/local/bin grpcurl 252 | sudo chmod +x /usr/local/bin/grpcurl 253 | echo "grpcurl installed at: $(which grpcurl)" 254 | echo "grpcurl version: $(grpcurl --version)" 255 | 256 | - name: ✅ Validate gRPC Connections with Mirror Node 257 | env: 258 | INSTALL_MIRROR_NODE: true 259 | run: | 260 | chmod +x ${GITHUB_WORKSPACE}/scripts/check-grpc-connections.sh 261 | bash ${GITHUB_WORKSPACE}/scripts/check-grpc-connections.sh 262 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /action.yml: -------------------------------------------------------------------------------- 1 | name: "Hiero Solo Action" 2 | description: "Run a Hiero based network by using the solo tool" 3 | inputs: 4 | installMirrorNode: 5 | description: "Defines if a mirror node should be installed" 6 | required: true 7 | default: false 8 | type: boolean 9 | hieroVersion: 10 | description: "Version of Hiero consensus node to be used" 11 | required: false 12 | default: "v0.66.0" 13 | mirrorNodeVersion: 14 | description: "Version of the mirror node to be used" 15 | required: false 16 | default: "v0.138.0" 17 | mirrorNodePortRest: 18 | description: "Port for Mirror Node REST API" 19 | required: false 20 | default: "5551" 21 | mirrorNodePortGrpc: 22 | description: "Port for Mirror Node gRPC" 23 | required: false 24 | default: "5600" 25 | mirrorNodePortWeb3Rest: 26 | description: "Port for Mirror Node WEB3 REST" 27 | required: false 28 | default: "8545" 29 | installRelay: 30 | description: "Install JSON-RPC-Relay" 31 | required: false 32 | default: false 33 | type: boolean 34 | relayPort: 35 | description: "Port for JSON-RPC-Relay" 36 | required: false 37 | default: "7546" 38 | grpcProxyPort: 39 | description: "Port for gRPC Proxy" 40 | required: false 41 | default: "9998" 42 | dualModeGrpcProxyPort: 43 | description: "Port for the gRPC Proxy of the second consensus node (only if dual mode is enabled)" 44 | required: false 45 | default: "9999" 46 | haproxyPort: 47 | description: "Port for HAProxy" 48 | required: false 49 | default: "50211" 50 | soloVersion: 51 | description: "Version of Solo CLI to install" 52 | required: false 53 | default: "0.46.1" 54 | javaRestApiPort: 55 | description: "Port for Java-based REST API" 56 | required: false 57 | default: "8084" 58 | dualMode: 59 | description: "Enable dual mode to deploy two consensus nodes" 60 | required: false 61 | default: false 62 | type: boolean 63 | hbarAmount: 64 | description: "Amount of HBAR to be assigned to the created accounts" 65 | required: false 66 | default: "10000000" 67 | outputs: 68 | accountId: 69 | description: "Account ID of the generated ED25519 account (default for simplicity)" 70 | value: ${{ steps.create-ed25519.outputs.accountId }} 71 | publicKey: 72 | description: "Public key of the generated ED25519 account (default for simplicity)" 73 | value: ${{ steps.create-ed25519.outputs.publicKey }} 74 | privateKey: 75 | description: "Private key of the generated ED25519 account (default for simplicity)" 76 | value: ${{ steps.create-ed25519.outputs.privateKey }} 77 | deployment: 78 | description: "Name of the Solo deployment created by the action" 79 | value: "solo-deployment" 80 | ecdsaAccountId: 81 | description: "ECDSA account id of generated account" 82 | value: ${{ steps.create-ecdsa.outputs.accountId }} 83 | ecdsaPublicKey: 84 | description: "ECDSA public key of generated account" 85 | value: ${{ steps.create-ecdsa.outputs.publicKey }} 86 | ecdsaPrivateKey: 87 | description: "ECDSA private key of generated account" 88 | value: ${{ steps.create-ecdsa.outputs.privateKey }} 89 | ed25519AccountId: 90 | description: "ED25519 account id of generated account" 91 | value: ${{ steps.create-ed25519.outputs.accountId }} 92 | ed25519PublicKey: 93 | description: "ED25519 public key of generated account" 94 | value: ${{ steps.create-ed25519.outputs.publicKey }} 95 | ed25519PrivateKey: 96 | description: "ED25519 private key of generated account" 97 | value: ${{ steps.create-ed25519.outputs.privateKey }} 98 | runs: 99 | using: "composite" 100 | steps: 101 | - name: Print inputs 102 | shell: bash 103 | run: | 104 | echo "installMirrorNode: ${{ inputs.installMirrorNode }}" 105 | echo "is installMirrorNode: ${{ inputs.installMirrorNode == 'true' }}" 106 | 107 | - name: Setup Java 108 | uses: actions/setup-java@99b8673ff64fbf99d8d325f52d9a5bdedb8483e9 # v4.2.1 109 | with: 110 | distribution: temurin 111 | java-version: 21 112 | 113 | - name: Setup Node 114 | uses: actions/setup-node@1e60f620b9541d16bece96c5465dc8ee9832be0b # v4.0.3 115 | with: 116 | node-version: 22 117 | 118 | - name: Install WGet CLI 119 | shell: bash 120 | run: sudo apt-get update && sudo apt-get install -y wget 121 | 122 | - name: Install Python 123 | uses: actions/setup-python@0b93645e9fea7318ecaed2b359559ac225c90a2b # v5.3.0 124 | with: 125 | python-version: "3.10" 126 | 127 | - name: Setup Kind 128 | uses: helm/kind-action@a1b0e391336a6ee6713a0583f8c6240d70863de3 # v1.12.0 129 | with: 130 | install_only: true 131 | node_image: kindest/node:v1.31.4@sha256:2cb39f7295fe7eafee0842b1052a599a4fb0f8bcf3f83d96c7f4864c357c6c30 132 | version: v0.26.0 133 | kubectl_version: v1.31.4 134 | verbosity: 3 135 | wait: 120s 136 | 137 | - name: Cleanup Previous Runs 138 | shell: bash 139 | run: | 140 | rm -rf ~/.solo 141 | kind delete cluster --name solo-e2e || true 142 | 143 | - name: Install Solo 144 | shell: bash 145 | run: npm install -g @hashgraph/solo@${{ inputs.soloVersion }} 146 | 147 | # Solo version has changed command values after 0.44.0. Commands should be executed based on 148 | # https://github.com/hiero-ledger/solo/blob/main/docs/site/content/en/docs/cli-migrations.md 149 | - name: Check Solo Version 150 | id: check-solo-version 151 | shell: bash 152 | run: | 153 | version=$(solo --version | grep Version | awk '{print $3}') 154 | solo_ge_0440=$([[ "$(printf '%s\n' "${version}" "0.44.0" | sort -V | head -n1)" == "0.44.0" ]] && echo "true" || echo "false") 155 | echo "solo-ge-0440=${solo_ge_0440}" >> "${GITHUB_OUTPUT}" 156 | 157 | - name: Deploy Solo Test Network 158 | shell: bash 159 | env: 160 | SOLO_CLUSTER_NAME: solo-e2e 161 | SOLO_NAMESPACE: solo 162 | SOLO_CLUSTER_SETUP_NAMESPACE: solo-cluster 163 | SOLO_DEPLOYMENT: solo-deployment 164 | HIERO_VERSION: ${{ inputs.hieroVersion }} 165 | SOLO_GE_0440: ${{ steps.check-solo-version.outputs.solo-ge-0440 }} 166 | DUAL_MODE: ${{ inputs.dualMode }} 167 | run: | 168 | # Create a Kubernetes cluster using kind 169 | kind create cluster -n ${SOLO_CLUSTER_NAME} 170 | 171 | # Initialize the Solo CLI configuration 172 | solo init --dev 173 | 174 | # Determine number of nodes based on dual mode 175 | if [[ "${DUAL_MODE}" == "true" ]]; then 176 | NUM_NODES=2 177 | NODE_IDS="node1,node2" 178 | else 179 | NUM_NODES=1 180 | NODE_IDS="node1" 181 | fi 182 | 183 | echo "Deploying ${NUM_NODES} consensus node(s)..." 184 | 185 | if [[ "${SOLO_GE_0440}" == "true" ]]; then 186 | echo "::debug::Using Solo CLI commands for version >= 0.44.0" 187 | # Connect the Solo CLI to the kind cluster using a cluster reference name 188 | solo cluster-ref config connect --cluster-ref kind-${SOLO_CLUSTER_NAME} --context kind-${SOLO_CLUSTER_NAME} --dev 189 | # Create deployment 190 | solo deployment config create -n ${SOLO_NAMESPACE} --deployment ${SOLO_DEPLOYMENT} --dev 191 | 192 | # Add the kind cluster to the deployment 193 | solo deployment cluster attach --deployment ${SOLO_DEPLOYMENT} --cluster-ref kind-${SOLO_CLUSTER_NAME} --num-consensus-nodes ${NUM_NODES} --dev 194 | 195 | # Generate node keys 196 | solo keys consensus generate --gossip-keys --tls-keys -i ${NODE_IDS} --deployment ${SOLO_DEPLOYMENT} --dev 197 | 198 | # Setup the Solo cluster 199 | solo cluster-ref config setup -s ${SOLO_CLUSTER_NAME} --dev 200 | 201 | # Deploy network 202 | solo consensus network deploy -i ${NODE_IDS} --deployment ${SOLO_DEPLOYMENT} --release-tag ${HIERO_VERSION} --dev 203 | 204 | # Setup and start nodes 205 | solo consensus node setup -i ${NODE_IDS} --deployment ${SOLO_DEPLOYMENT} --release-tag ${HIERO_VERSION} --quiet-mode --dev 206 | solo consensus node start -i ${NODE_IDS} --deployment ${SOLO_DEPLOYMENT} --dev 207 | else 208 | echo "::debug::Using Solo CLI commands for version < 0.44.0" 209 | solo cluster-ref connect --cluster-ref kind-${SOLO_CLUSTER_NAME} --context kind-${SOLO_CLUSTER_NAME} --dev 210 | solo deployment create -n ${SOLO_NAMESPACE} --deployment ${SOLO_DEPLOYMENT} --dev 211 | solo deployment add-cluster --deployment ${SOLO_DEPLOYMENT} --cluster-ref kind-${SOLO_CLUSTER_NAME} --num-consensus-nodes ${NUM_NODES} --dev 212 | 213 | # Generate node keys 214 | solo node keys --gossip-keys --tls-keys -i ${NODE_IDS} --deployment ${SOLO_DEPLOYMENT} --dev 215 | 216 | solo cluster-ref setup -s ${SOLO_CLUSTER_NAME} --dev 217 | 218 | # Deploy network 219 | solo network deploy -i ${NODE_IDS} --deployment ${SOLO_DEPLOYMENT} --release-tag ${HIERO_VERSION} --dev 220 | 221 | # Setup and start nodes 222 | solo node setup -i ${NODE_IDS} --deployment ${SOLO_DEPLOYMENT} --release-tag ${HIERO_VERSION} --quiet-mode --dev 223 | solo node start -i ${NODE_IDS} --deployment ${SOLO_DEPLOYMENT} --dev 224 | fi 225 | 226 | # Debug: List services in the solo namespace 227 | echo "Listing services in namespace ${SOLO_NAMESPACE}:" 228 | kubectl get svc -n ${SOLO_NAMESPACE} 229 | 230 | # Debug: Show current user 231 | echo "Current user: $(whoami)" 232 | 233 | # Add entries to /etc/hosts for easier access (requires sudo) 234 | echo "Attempting to add entries to /etc/hosts..." 235 | if sudo -n true 2>/dev/null; then 236 | echo "127.0.0.1 network-node1-svc.${SOLO_NAMESPACE}.svc.cluster.local" | sudo tee -a /etc/hosts 237 | echo "127.0.0.1 envoy-proxy-node1-svc.${SOLO_NAMESPACE}.svc.cluster.local" | sudo tee -a /etc/hosts 238 | if [[ "${DUAL_MODE}" == "true" ]]; then 239 | echo "127.0.0.1 network-node2-svc.${SOLO_NAMESPACE}.svc.cluster.local" | sudo tee -a /etc/hosts 240 | echo "127.0.0.1 envoy-proxy-node2-svc.${SOLO_NAMESPACE}.svc.cluster.local" | sudo tee -a /etc/hosts 241 | fi 242 | echo "Successfully added entries to /etc/hosts" 243 | else 244 | echo "⚠️ No sudo access available, skipping /etc/hosts update" 245 | echo "Nodes can still be accessed via localhost:50211 and localhost:50212" 246 | fi 247 | 248 | # Port forward HAProxy for node1 (only if service exists) 249 | if kubectl get svc haproxy-node1-svc -n ${SOLO_NAMESPACE} >/dev/null 2>&1; then 250 | kubectl port-forward svc/haproxy-node1-svc -n ${SOLO_NAMESPACE} ${{ inputs.haproxyPort }}:50211 & 251 | else 252 | echo "HAProxy service haproxy-node1-svc not found, skipping port-forward" 253 | fi 254 | 255 | # Port forward services for node2 if dual mode is enabled 256 | if [[ "${DUAL_MODE}" == "true" ]]; then 257 | # Port forward HAProxy for node2 (only if service exists) 258 | if kubectl get svc haproxy-node2-svc -n ${SOLO_NAMESPACE} >/dev/null 2>&1; then 259 | kubectl port-forward svc/haproxy-node2-svc -n ${SOLO_NAMESPACE} 51211:50211 & 260 | echo "HAProxy for node2 is accessible on port 51211" 261 | else 262 | echo "HAProxy service haproxy-node2-svc not found, skipping port-forward" 263 | fi 264 | 265 | # Port forward gRPC proxy for node2 (only if service exists) 266 | if kubectl get svc envoy-proxy-node2-svc -n ${SOLO_NAMESPACE} >/dev/null 2>&1; then 267 | kubectl port-forward svc/envoy-proxy-node2-svc -n ${SOLO_NAMESPACE} ${{ inputs.dualModeGrpcProxyPort }}:8080 & 268 | echo "gRPC proxy for node2 is accessible on port ${{ inputs.dualModeGrpcProxyPort }}" 269 | else 270 | echo "gRPC proxy service envoy-proxy-node2-svc not found, skipping port-forward" 271 | fi 272 | fi 273 | 274 | # Port forward gRPC proxy (only if service exists) 275 | if kubectl get svc envoy-proxy-node1-svc -n ${SOLO_NAMESPACE} >/dev/null 2>&1; then 276 | kubectl port-forward svc/envoy-proxy-node1-svc -n ${SOLO_NAMESPACE} ${{ inputs.grpcProxyPort }}:8080 & 277 | else 278 | echo "gRPC proxy service envoy-proxy-node1-svc not found, skipping port-forward" 279 | fi 280 | 281 | - name: Deploy MirrorNode 282 | if: ${{ inputs.installMirrorNode == 'true' }} # see https://github.com/actions/runner/issues/2238 283 | shell: bash 284 | env: 285 | SOLO_NAMESPACE: solo 286 | SOLO_DEPLOYMENT: solo-deployment 287 | SOLO_CLUSTER_NAME: solo-e2e 288 | MIRROR_NODE_VERSION: ${{ inputs.mirrorNodeVersion }} 289 | SOLO_GE_0440: ${{ steps.check-solo-version.outputs.solo-ge-0440 }} 290 | run: | 291 | if [[ "${SOLO_GE_0440}" == "true" ]]; then 292 | solo mirror node add --cluster-ref kind-${SOLO_CLUSTER_NAME} --deployment ${SOLO_DEPLOYMENT} --mirror-node-version ${MIRROR_NODE_VERSION} --pinger --dev 293 | else 294 | solo mirror-node deploy --cluster-ref kind-${SOLO_CLUSTER_NAME} --deployment ${SOLO_DEPLOYMENT} --mirror-node-version ${MIRROR_NODE_VERSION} --pinger --dev 295 | fi 296 | 297 | # Debug: List services in the solo namespace 298 | echo "Listing services in namespace ${SOLO_NAMESPACE}:" 299 | kubectl get svc -n ${SOLO_NAMESPACE} 300 | # Port forward mirror node REST API (only if service exists) 301 | if kubectl get svc mirror-1-rest -n ${SOLO_NAMESPACE} >/dev/null 2>&1; then 302 | kubectl port-forward svc/mirror-1-rest -n ${SOLO_NAMESPACE} ${{ inputs.mirrorNodePortRest }}:80 & 303 | else 304 | echo "Mirror node service mirror-1-rest not found, skipping port-forward" 305 | fi 306 | # Port forward mirror node gRPC (only if service exists) 307 | if kubectl get svc mirror-1-grpc -n ${SOLO_NAMESPACE} >/dev/null 2>&1; then 308 | kubectl port-forward svc/mirror-1-grpc -n ${SOLO_NAMESPACE} ${{ inputs.mirrorNodePortGrpc }}:5600 & 309 | else 310 | echo "Mirror node service mirror-1-grpc not found, skipping port-forward" 311 | fi 312 | # Port forward mirror node web3 (only if service exists) 313 | if kubectl get svc mirror-1-web3 -n ${SOLO_NAMESPACE} >/dev/null 2>&1; then 314 | kubectl port-forward svc/mirror-1-web3 -n ${SOLO_NAMESPACE} ${{ inputs.mirrorNodePortWeb3Rest }}:80 & 315 | else 316 | echo "Mirror node service mirror-1-web3 not found, skipping port-forward" 317 | fi 318 | # Port forward Java REST API (only if service exists) 319 | if kubectl get svc mirror-1-restjava -n ${SOLO_NAMESPACE} >/dev/null 2>&1; then 320 | kubectl port-forward svc/mirror-1-restjava -n ${SOLO_NAMESPACE} ${{ inputs.javaRestApiPort }}:80 & 321 | else 322 | echo "Java REST API service mirror-1-restjava not found, skipping port-forward" 323 | fi 324 | 325 | - name: Deploy JSON-RPC-Relay 326 | if: ${{ inputs.installRelay == 'true' }} 327 | shell: bash 328 | env: 329 | SOLO_NAMESPACE: solo 330 | SOLO_DEPLOYMENT: solo-deployment 331 | SOLO_GE_0440: ${{ steps.check-solo-version.outputs.solo-ge-0440 }} 332 | run: | 333 | echo "Installing JSON-RPC-Relay..." 334 | if [[ "${SOLO_GE_0440}" == "true" ]]; then 335 | solo relay node add -i node1 --deployment ${SOLO_DEPLOYMENT} --dev 336 | else 337 | solo relay deploy -i node1 --deployment ${SOLO_DEPLOYMENT} --dev 338 | fi 339 | echo "JSON-RPC-Relay installed successfully" 340 | 341 | # Debug: List services in the solo namespace 342 | echo "Listing services in namespace ${SOLO_NAMESPACE}:" 343 | kubectl get svc -n ${SOLO_NAMESPACE} 344 | 345 | # Port forward JSON-RPC-Relay (only if service exists) 346 | if kubectl get svc relay-node1-hedera-json-rpc-relay -n ${SOLO_NAMESPACE} >/dev/null 2>&1; then 347 | kubectl port-forward svc/relay-node1-hedera-json-rpc-relay -n ${SOLO_NAMESPACE} ${{ inputs.relayPort }}:7546 & 348 | else 349 | echo "JSON-RPC-Relay service relay-node1-hedera-json-rpc-relay not found, Skipping port-forward" 350 | fi 351 | 352 | - name: Create ECDSA Account 353 | id: create-ecdsa 354 | shell: bash 355 | env: 356 | GITHUB_ACTION_PATH: ${{ github.action_path }} 357 | SOLO_NAMESPACE: solo 358 | SOLO_DEPLOYMENT: solo-deployment 359 | HBAR_AMOUNT: ${{ inputs.hbarAmount || 10000000 }} 360 | SOLO_GE_0440: ${{ steps.check-solo-version.outputs.solo-ge-0440 }} 361 | run: | 362 | echo "Creating ECDSA account..." 363 | 364 | if [[ "${SOLO_GE_0440}" == "true" ]]; then 365 | solo ledger account create --generate-ecdsa-key --deployment ${SOLO_DEPLOYMENT} --dev > account_create_output_ecdsa.txt 366 | else 367 | solo account create --generate-ecdsa-key --deployment ${SOLO_DEPLOYMENT} --dev > account_create_output_ecdsa.txt 368 | fi 369 | 370 | cat account_create_output_ecdsa.txt 371 | JSON=$(cat account_create_output_ecdsa.txt | python3 ${GITHUB_ACTION_PATH}/extractAccountAsJson.py) || { 372 | echo "Error: Python script extractAccountAsJson.py failed" 373 | exit 1 374 | } 375 | 376 | export ACCOUNT_ID=$(echo ${JSON} | jq -r '.accountId') 377 | export ACCOUNT_PUBLIC_KEY=$(echo ${JSON} | jq -r '.publicKey') 378 | export ACCOUNT_PRIVATE_KEY=$(kubectl get secret account-key-${ACCOUNT_ID} -n ${SOLO_NAMESPACE} -o jsonpath='{.data.privateKey}' | base64 -d | xargs) 379 | 380 | if [[ "${SOLO_GE_0440}" == "true" ]]; then 381 | solo ledger account update --account-id "${ACCOUNT_ID}" --hbar-amount "${HBAR_AMOUNT}" --deployment "${SOLO_DEPLOYMENT}" --dev 382 | else 383 | solo account update --account-id "${ACCOUNT_ID}" --hbar-amount "${HBAR_AMOUNT}" --deployment "${SOLO_DEPLOYMENT}" --dev 384 | fi 385 | 386 | echo "accountId=${ACCOUNT_ID}" 387 | echo "publicKey=${ACCOUNT_PUBLIC_KEY}" 388 | echo "privateKey=${ACCOUNT_PRIVATE_KEY}" 389 | echo "accountId=${ACCOUNT_ID}" >> ${GITHUB_OUTPUT} 390 | echo "publicKey=${ACCOUNT_PUBLIC_KEY}" >> ${GITHUB_OUTPUT} 391 | echo "privateKey=${ACCOUNT_PRIVATE_KEY}" >> ${GITHUB_OUTPUT} 392 | 393 | - name: Create ED25519 Account 394 | id: create-ed25519 395 | shell: bash 396 | env: 397 | GITHUB_ACTION_PATH: ${{ github.action_path }} 398 | SOLO_NAMESPACE: solo 399 | SOLO_DEPLOYMENT: solo-deployment 400 | HBAR_AMOUNT: ${{ inputs.hbarAmount || 10000000 }} 401 | SOLO_GE_0440: ${{ steps.check-solo-version.outputs.solo-ge-0440 }} 402 | run: | 403 | echo "Creating ED25519 account..." 404 | if [[ "${SOLO_GE_0440}" == "true" ]]; then 405 | solo ledger account create --deployment ${SOLO_DEPLOYMENT} --dev > account_create_output_ed25519.txt 406 | else 407 | solo account create --deployment ${SOLO_DEPLOYMENT} --dev > account_create_output_ed25519.txt 408 | fi 409 | 410 | cat account_create_output_ed25519.txt 411 | JSON=$(cat account_create_output_ed25519.txt | python3 ${GITHUB_ACTION_PATH}/extractAccountAsJson.py) || { 412 | echo "Error: Python script extractAccountAsJson.py failed" 413 | exit 1 414 | } 415 | 416 | export ACCOUNT_ID=$(echo ${JSON} | jq -r '.accountId') 417 | export ACCOUNT_PUBLIC_KEY=$(echo ${JSON} | jq -r '.publicKey') 418 | export ACCOUNT_PRIVATE_KEY=$(kubectl get secret account-key-${ACCOUNT_ID} -n ${SOLO_NAMESPACE} -o jsonpath='{.data.privateKey}' | base64 -d | xargs) 419 | 420 | if [[ "${SOLO_GE_0440}" == "true" ]]; then 421 | solo ledger account update --account-id "${ACCOUNT_ID}" --hbar-amount "${HBAR_AMOUNT}" --deployment "${SOLO_DEPLOYMENT}" --dev 422 | else 423 | solo account update --account-id "${ACCOUNT_ID}" --hbar-amount "${HBAR_AMOUNT}" --deployment "${SOLO_DEPLOYMENT}" --dev 424 | fi 425 | 426 | echo "accountId=${ACCOUNT_ID}" 427 | echo "publicKey=${ACCOUNT_PUBLIC_KEY}" 428 | echo "privateKey=${ACCOUNT_PRIVATE_KEY}" 429 | echo "accountId=${ACCOUNT_ID}" >> ${GITHUB_OUTPUT} 430 | echo "publicKey=${ACCOUNT_PUBLIC_KEY}" >> ${GITHUB_OUTPUT} 431 | echo "privateKey=${ACCOUNT_PRIVATE_KEY}" >> ${GITHUB_OUTPUT} 432 | 433 | # Ref: https://haya14busa.github.io/github-action-brandings/ 434 | branding: 435 | icon: "share-2" 436 | color: "black" 437 | --------------------------------------------------------------------------------