Skip to main content

Set up a localnet

Goal

This article will guide you to set up a localnet that serves as a sandbox for developing Hathor nodes, wallets, blueprints, nano contracts, DApps, and integrations with Hathor Network.

The localnet is a sandboxed local instance of Hathor system, fully isolated from public Hathor Network mainnet and testnet, and comprises four interconnected autonomous components:

  • Full node (Hathor core)
  • Mining server (Transaction mining service)
  • CPU miner
  • Headless wallet

Requirements

Step-by-step

  1. Install localnet.
  2. Test localnet.
  3. Test nano contracts.
  4. Test blueprint deployment.
  5. Test end-user experience.

Step 1: install localnet

The localnet installation method varies depending on the software you are developing. To develop DApps, wallets, or integrations with Hathor Network, you can install the entire localnet via Docker compose. In turn, to develop blueprints or Hathor nodes, you need to install Hathor core from source code and the other three components via Docker compose.

If you are developing integrations, DApps, or wallets, follow this procedure:

  1. Open a terminal.
  2. Change the working directory to where you want to install the localnet — namely, configuration files and database.
  3. Create the directory data, to store the full node's database (mainly the ledger).
  4. Create the file docker-compose.yml with the configuration of your localnet:
<working_directory>/docker-compose.yml
networks:
hathor:
driver: bridge
name: hathor
services:
full-node:
image: hathornetwork/hathor-core
ports:
- "8080:8080"
volumes:
- ${PWD}/data:/data
- ${PWD}/localnet.yml:/app/localnet.yml:ro
environment:
- HATHOR_CONFIG_YAML=/app/localnet.yml
- HATHOR_DATA=/data
- HATHOR_LISTEN=tcp:40403
- HATHOR_STATUS=8080
- HATHOR_WALLET_INDEX=true
- HATHOR_CACHE=true
- HATHOR_CACHE_SIZE=100000
- HATHOR_ALLOW_MINING_WITHOUT_PEERS=true
networks:
hathor:
aliases:
- full-node
command: run_node
healthcheck:
test: ["CMD", "sh", "-c", "timeout 1 bash -c 'echo > /dev/tcp/localhost/8080'"]
start_period: 2s
timeout: 2s
retries: 10
interval: 2s
mining-server:
image: hathornetwork/tx-mining-service
depends_on:
full-node:
condition: service_healthy
ports:
- "9000:9000"
- "9080:9080"
networks:
hathor:
aliases:
- mining-server
command: >
--testnet
--address WiGFcSYHhfRqWJ7PXYvhjULXtXCYD1VFdS
--stratum-port 9000
--api-port 9080
http://full-node:8080
healthcheck:
test: ["CMD", "sh", "-c", "timeout 1 nc -z 0.0.0.0 9000 && timeout 1 nc -z 0.0.0.0 9080"]
start_period: 2s
timeout: 2s
retries: 10
interval: 2s
cpu-miner:
image: hathornetwork/cpuminer
depends_on:
mining-server:
condition: service_healthy
networks:
hathor:
aliases:
- cpu-miner
entrypoint: ["/bin/sh", "-c"]
command: >
"minerd \
--algo sha256d \
--threads 1 \
--coinbase-addr WiGFcSYHhfRqWJ7PXYvhjULXtXCYD1VFdS \
--url stratum+tcp://mining-server:9000 \
2>&1 | tee /tmp/miner.log"
healthcheck:
test: ["CMD", "sh", "-c", "grep -q 'hashes' /tmp/miner.log"]
start_period: 2s
timeout: 2s
retries: 10
interval: 2s
wallet:
image: hathornetwork/hathor-wallet-headless
depends_on:
cpu-miner:
condition: service_healthy
ports:
- "8000:8000"
environment:
- HEADLESS_NETWORK=privatenet
- HEADLESS_SEEDS=genesis alice
- HEADLESS_SEED_GENESIS=avocado spot town typical traffic vault danger century property shallow divorce festival spend attack anchor afford rotate green audit adjust fade wagon depart level
- HEADLESS_SEED_ALICE=music endless reduce plunge accident multiply two curtain match balance present belt price burger mother crisp sock tumble napkin leopard unit upset original cause
- HEADLESS_SERVER=http://full-node:8080/v1a/
- HEADLESS_TX_MINING_URL=http://mining-server:9080
- HEADLESS_HTTP_PORT=8000
- HEADLESS_CONSOLE_LEVEL=debug
networks:
hathor:
aliases:
- wallet
healthcheck:
test: ["CMD", "sh", "-c", "timeout 1 nc -z 0.0.0.0 8000"]
start_period: 2s
timeout: 2s
retries: 10
interval: 2s
  1. Create the file localnet.yml to complement the full node configuration:
<working_directory>/localnet.yml
P2PKH_VERSION_BYTE: x49
MULTISIG_VERSION_BYTE: x87
NETWORK_NAME: local-privatenet
BOOTSTRAP_DNS: []

# Ledger genesis
GENESIS_OUTPUT_SCRIPT: 76a91466665b27f7dbc4c8c089d2f686c170c74d66f0b588ac
GENESIS_BLOCK_TIMESTAMP: 1643902665
GENESIS_BLOCK_NONCE: 4784939
GENESIS_BLOCK_HASH: 00000334a21fbb58b4db8d7ff282d018e03e2977abd3004cf378fb1d677c3967
GENESIS_TX1_NONCE: 0
GENESIS_TX1_HASH: 54165cef1fd4cf2240d702b8383c307c822c16ca407f78014bdefa189a7571c2
GENESIS_TX2_NONCE: 0
GENESIS_TX2_HASH: 039906854ce6309b3180945f2a23deb9edff369753f7082e19053f5ac11bfbae

# Genesis wallet:
# avocado spot town typical traffic vault danger century property shallow divorce festival spend attack anchor afford rotate green audit adjust fade wagon depart level

MIN_TX_WEIGHT_K: 0
MIN_TX_WEIGHT_COEFFICIENT: 0
MIN_TX_WEIGHT: 1
REWARD_SPEND_MIN_BLOCKS: 1

CHECKPOINTS: []

extends: testnet.yml
  1. Start the four services that make up your localnet:
docker compose up

Wait a few seconds while all four services initialize. Keep this terminal open to monitor their logs. This will allow you to confirm that your localnet is functioning properly and to identify errors in the software you are developing.

  1. Open a new terminal.
  2. Change the working directory to where you installed the localnet.
  3. Confirm that all four services have started correctly:
docker compose ps

You should see four running containers and their STATUS should be marked as (healthy). Note that this health check only confirms that the applications have been initialized and are running. It does not detect behavioral failures, nor does it serve as continuous monitoring.

Finally, by checking the logs in the other terminal(s), you can confirm that your localnet is ready for use. After a few seconds, you should see the CPU miner hashing, the mining server finding and submitting blocks to the full node, and the full node adding new blocks to the ledger.

Step 2: test localnet

Now, you need to test whether the wallets are interacting correctly with the blockchain and whether transactions are being processed properly. There are two pre-configured wallets in localnet: Genesis (genesis) and Alice (alice). Use the available terminal — i.e., the one that is not receiving the localnet logs — to perform the following procedure:

  1. Start Genesis wallet:
curl -X POST \
-H "Content-Type: application/json" \
-d '{
"wallet-id": "genesis",
"seedKey": "genesis"
}' \
http://localhost:8000/start | jq
  1. Check Genesis' balance:
curl -X GET \
-H "X-Wallet-Id: genesis" \
http://localhost:8000/wallet/balance | jq

Genesis wallet is configured with an initial balance of 1B HTR tokens when running the entire localnet via Docker. On the other hand, It starts with a zero balance when running the full node from source code.

  1. Start Alice wallet:
curl -X POST \
-H "Content-Type: application/json" \
-d '{
"wallet-id": "alice",
"seedKey": "alice"
}' \
http://localhost:8000/start | jq
  1. Check Alice's balance:
curl -X GET \
-H "X-Wallet-Id: alice" \
http://localhost:8000/wallet/balance | jq

Alice wallet is configured to receive block mining rewards in HTR. You will need Alice’s funds (and Genesis’ if available) to test the localnet and, afterward, your software.

  1. Use Alice wallet to create a token:
API request
curl -X POST \
-H "X-Wallet-Id: alice" \
-H "Content-type: application/json" \
-d '{
"name": "alice-coin",
"symbol": "ALICE",
"amount": 1000
}' \
http://localhost:8000/wallet/create-token | jq

A token is identified by its token UID, which is the hash of the transaction that created it. In the following example, it is 00000943573723a28e3dd980c10e08419d0e00bc647a95f4ca9671ebea7d5669:

API response
{
"success": true,
"configurationString": "[alice-coin:ALICE:00000943573723a28e3dd980c10e08419d0e00bc647a95f4ca9671ebea7d5669:f57c591a]",
"inputs": [ ... ],
"outputs": [ ... ],
"signalBits": 0,
"version": 2,
"weight": 19.362482584834382,
"nonce": 2073499,
"timestamp": 1733336251,
"parents": [ ... ],
"tokens": [],
"hash": "00000943573723a28e3dd980c10e08419d0e00bc647a95f4ca9671ebea7d5669",
"_dataToSignCache": { ... },
"name": "alice-coin",
"symbol": "ALICE"
}
tip

<Placeholders>: in the code samples of this article, as in all Hathor docs, <placeholders> are always wrapped by angle brackets < >. You shall interpret or replace a <placeholder> with a value according to the context. Whenever replacing a <placeholder> like this one with a value, do not wrap the value with quotes. Quotes, when necessary, will be indicated, wrapping the "<placeholder>" like this one.

  1. Send an amount of the created token from Alice to Genesis, replacing <newly_created_token_UID> with the hash of the resulting transaction from the previous substep:
curl -X POST \
-H "X-Wallet-Id: alice" \
-H "Content-Type: application/json" \
-d '{
"address" : "WRTFYzhTHkfYwub8EWVtAcUgbdUpsYMBpb",
"value" : 100,
"token": "<newly_created_token_UID>"
}' \
http://localhost:8000/wallet/simple-send-tx | jq
  1. Confirm that Genesis received the transfer from Alice, replacing <newly_created_token_UID> with the UID of the created token:
curl -X GET \
-H "X-Wallet-Id: genesis" \
http://localhost:8000/wallet/balance?token=<newly_created_token_UID> | jq

Step 3: test nano contracts

At the time of writing (Mar 03, 2025) nano contracts are only available if you are running the full node from source code.

  1. Use Alice wallet to create a nano contract from the bet blueprint, replacing <newly_created_token_UID> with the UID of the created token:
curl -X POST \
-H "X-Wallet-Id: alice" \
-H 'Content-Type: application/json' \
-d '{
"blueprint_id": "3cb032600bdf7db784800e4ea911b10676fa2f67591f82bb62628c234e771595",
"address": "WiGFcSYHhfRqWJ7PXYvhjULXtXCYD1VFdS",
"data": {
"actions": [],
"args": [
"0000298f16599418b0475762c9ce570fe966fd8a62fd933888a96a16c0b893b7",
"<newly_created_token_UID>",
1891123200
]
}
}' \
http://localhost:8000/wallet/nano-contracts/create | jq

Analogous to the token UID, the contract ID is the hash of the transaction that created the contract.

  1. Use Alice wallet to execute the contract, replacing <newly_created_token_UID> with the token used in the previous substep, and <newly_created_contract_ID> with the hash of the corresponding resulting transaction:
curl -X POST \
-H "X-Wallet-Id: alice" \
-H 'Content-Type: application/json' \
-d '{
"nc_id": "<newly_created_contract_ID>",
"method": "bet",
"address": "WiGFcSYHhfRqWJ7PXYvhjULXtXCYD1VFdS",
"data": {
"actions": [
{
"type": "deposit",
"token": "<newly_created_token_UID>",
"amount": "100"
}
],
"args": [
"WiGFcSYHhfRqWJ7PXYvhjULXtXCYD1VFdS",
"Real-Madrid2x2Barcelona"
]
}
}' \
http://localhost:8000/wallet/nano-contracts/execute | jq

The contract history lists all transactions that executed the contract. Transactions whose contract execution was unsuccessful will be marked as voided.

  1. Check the contract history to verify whether the contract execution in the previous step was successful, replacing <newly_created_contract_ID> with the respective contract ID:
curl -X GET \
-H "X-Wallet-Id: alice" \
http://localhost:8000/wallet/nano-contracts/history?id=<ID_contract> | jq

Step 4: test blueprint deployment

This step is specific to developing new blueprints.

  1. Go to the full node's output terminal.
  2. Stop the full node.
  3. Open the file hathor/nanocontracts/blueprints/__init__.py.
  4. Add your blueprint to the catalog of built-in blueprints:
hathor-core/hathor/nanocontracts/blueprints/__init__.py
from typing import TYPE_CHECKING, Type

from hathor.nanocontracts.blueprints.bet import Bet
from hathor.nanocontracts.blueprints.dozer_pool import Dozer_Pool
from hathor.nanocontracts.blueprints.swap_demo import SwapDemo
from hathor.nanocontracts.blueprints.your_blueprint import YourBlueprint

if TYPE_CHECKING:
from hathor.nanocontracts.blueprint import Blueprint

_blueprints_mapper: dict[str, Type["Blueprint"]] = {
"Bet": Bet,
"Dozer_Pool": Dozer_Pool,
"SwapDemo": SwapDemo,
"YourBlueprint": YourBlueprint
}

__all__ = [
"Bet",
"Dozer_Pool",
"SwapDemo",
"YourBlueprint"
]
  1. Open the file hathor/conf/nano_testnet.yml.
  2. Define the blueprint ID by appending the following line and replacing only the blueprint name:
hathor-core/hathor/conf/nano_testnet.yml
...

# Genesis stuff

...

# tx weight parameters. With these settings tx weight is always 8

...

BLUEPRINTS:
3cb032600bdf7db784800e4ea911b10676fa2f67591f82bb62628c234e771595: Bet
27db2b0b1a943c2714fb19d190ce87dc0094bba463b26452dd98de21a42e96a0: Dozer_Pool
0000298f16599418b0475762c9ce570fe966fd8a62fd933888a96a16c0b893b7: SwapDemo
41c4c4f09455f89a00345c8ea50c166d7e41cf80952e76db41f5d7a3044298e7: YourBlueprint

REWARD_SPEND_MIN_BLOCKS: 3
info

A blueprint ID is a 32-byte hexadecimal integer. To add more blueprints, generate and use random 32-byte hexadecimal integers.

  1. Restart the full node and test the new blueprint, as you did in the previous step with the bet blueprint.

Step 5: test end-user experience

This step is specific to developing DApps.

To test your DApp's user experience, you need to add Hathor desktop wallet to your local development environment:

  1. Install Hathor desktop wallet.
  2. Start Hathor desktop wallet.
  3. Initialize Alice wallet by importing its seed:
music endless reduce plunge accident multiply two curtain match balance present belt price burger mother crisp sock tumble napkin leopard unit upset original cause
  1. Connect Hathor desktop wallet to your localnet:
    1. Navigate to Settings.
    2. Click Change network to open Network Settings.
    3. In the dropdown menu, select Custom network.
    4. Set Node URL to http://localhost:8080/v1a/.
    5. Set Transaction Mining Service URL to http://localhost:9080.
    6. Leave all other fields blank.
    7. Click Connect to save configuration.

Task completed

You now have a fully functional Hathor localnet in your development environment. The full node exposes its HTTP API on port 8080. To monitor its status, open a browser and navigate to http://localhost:8080/v1a/status/. The headless wallet exposes its HTTP API on port 8000. Note that only the main features of Hathor technology have been tested. To know how to test other features, see Hathor headless wallet pathway.

Finally, you may need to integrate other Docker containerized applications with the localnet. To do this, simply add these containers to Docker network hathor defined in docker-compose.yml. For example:

docker network connect hathor <other_docker_container>

Then, you can reference Hathor localnet components by their service names as defined in docker-compose.yml — e.g., http://wallet:8000.