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
- Docker v27.3.1
- Docker compose v2.29.7
- Available ports (not in use): 8000, 8080, 9000, 9080
Step-by-step
- Install localnet.
- Test localnet.
- Test nano contracts.
- Test blueprint deployment.
- 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.
- Integrations, DApps, or wallets
- Blueprints or Hathor nodes
If you are developing integrations, DApps, or wallets, follow this procedure:
- Open a terminal.
- Change the working directory to where you want to install the localnet — namely, configuration files and database.
- Create the directory
data
, to store the full node's database (mainly the ledger). - Create the file
docker-compose.yml
with the configuration of your localnet:
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
- Create the file
localnet.yml
to complement the full node configuration:
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
- 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.
- Open a new terminal.
- Change the working directory to where you installed the localnet.
- 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.
If you are developing blueprints or Hathor nodes, follow this procedure:
- Install Hathor core from source code.
- Open a terminal.
- Change the working directory to where you installed Hathor core — i.e., the parent directory of
hathor-core
. - Empty the subdirectory
data
. - Change the working directory to its child
hathor-core
. - Switch the repository
hathor-core
to the SDK branch:
git switch experimental/nano-testnet-v1.6.1
- Open the file
hathor/conf/nano_testnet.yml
. - At the end of the file, add the environment variable
REWARD_SPEND_MIN_BLOCKS: 1
. - Start the full node with data persistence:
poetry run hathor-cli run_node --nano-testnet --data ../data --listen tcp:40403 --status 8080 --wallet-index --x-localhost-only --allow-mining-without-peers --nc-history-index
Keep this terminal open to monitor the full node's output log.
- Open a new terminal.
- Change the working directory to where you installed the full node — namely,
hathor-core
anddata
. - Create the file
docker-compose.yml
with the remaining configuration of your localnet:
networks:
hathor:
driver: bridge
name: hathor
services:
mining-server:
image: hathornetwork/tx-mining-service
ports:
- "9000:9000"
- "9080:9080"
networks:
hathor:
aliases:
- mining-server
extra_hosts:
- "host.docker.internal:host-gateway"
command: >
--testnet
--address WiGFcSYHhfRqWJ7PXYvhjULXtXCYD1VFdS
--stratum-port 9000
--api-port 9080
http://host.docker.internal: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=testnet
- 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://host.docker.internal:8080/v1a/
- HEADLESS_TX_MINING_URL=http://mining-server:9080
- HEADLESS_HTTP_PORT=8000
- HEADLESS_CONSOLE_LEVEL=debug
networks:
hathor:
aliases:
- wallet
extra_hosts:
- "host.docker.internal:host-gateway"
healthcheck:
test: ["CMD", "sh", "-c", "timeout 1 nc -z 0.0.0.0 8000"]
start_period: 2s
timeout: 2s
retries: 10
interval: 2s
- Start the three services that, together with the full node, make up your localnet:
docker compose up
Wait a few seconds while the three services initialize. Keep this terminal open alongsige the full node's terminal output 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.
- Open a new terminal.
- Change the working directory to where you installed the localnet — namely, full node and Docker services.
- Confirm that all three services managed via Docker have started correctly:
docker compose ps
You should see three 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:
- Start Genesis wallet:
curl -X POST \
-H "Content-Type: application/json" \
-d '{
"wallet-id": "genesis",
"seedKey": "genesis"
}' \
http://localhost:8000/start | jq
- 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.
- Start Alice wallet:
curl -X POST \
-H "Content-Type: application/json" \
-d '{
"wallet-id": "alice",
"seedKey": "alice"
}' \
http://localhost:8000/start | jq
- 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.
- Use Alice wallet to create a token:
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
:
{
"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"
}
<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.
- 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
- 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.
- 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.
- 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.
- 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.
- Go to the full node's output terminal.
- Stop the full node.
- Open the file
hathor/nanocontracts/blueprints/__init__.py
. - Add your blueprint to the catalog of built-in blueprints:
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"
]
- Open the file
hathor/conf/nano_testnet.yml
. - Define the blueprint ID by appending the following line and replacing only the blueprint name:
...
# 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
A blueprint ID is a 32-byte hexadecimal integer. To add more blueprints, generate and use random 32-byte hexadecimal integers.
- 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:
- Install Hathor desktop wallet.
- Start Hathor desktop wallet.
- 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
- Connect Hathor desktop wallet to your localnet:
- Navigate to Settings.
- Click Change network to open Network Settings.
- In the dropdown menu, select Custom network.
- Set Node URL to
http://localhost:8080/v1a/
. - Set Transaction Mining Service URL to
http://localhost:9080
. - Leave all other fields blank.
- 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
.