A local BSV regtest network built on Teranode, for developing and testing wallets, services and apps on your own machine.
One command brings up a single regtest Teranode in Docker, with an ARC-compatible broadcast API, merkle proofs, a block every ten minutes and a faucet to fund your test addresses.
It replaces arc_regtest (SV Node + ARC), and the two can run side by side: they share no ports, containers, networks or volumes.
| Component | Version | Role |
|---|---|---|
| Teranode | v0.16.0 | The regtest node |
| Arcade | v0.16.0 | ARC-compatible API: broadcast transactions, follow their status, fetch merkle proofs |
| Merkle Service | v0.6.2 | Watches the node's gossip and gives Arcade the data for its merkle proofs |
| Autominer | Mines a block every 10 minutes (configurable) | |
| Faucet | A small Go tool that holds the mined coins and sends them to your addresses |
- Docker, with about 4 GB of memory free (the stack uses about 2.5 GB when idle)
- Go 1.27, for the faucet
./scripts/start.shThe first run writes .env with fresh secrets, starts the containers, switches Teranode on,
creates the faucet key, mines to height 101 so the faucet has spendable coins, and waits for
Arcade to be ready. It is safe to run again at any time.
Then:
./scripts/status.sh # containers, chain tip, faucet balance
./scripts/faucet.sh send <address> <satoshis> # fund a test address
./scripts/mine.sh 1 # mine a block now instead of waitingWhen you are done, ./scripts/stop.sh stops the stack and keeps the chain;
./scripts/reset.sh wipes the chain and starts over from nothing.
Point your ARC client at Arcade on http://127.0.0.1:23011. Note that there is no /v1
prefix: Arcade serves the ARC API at its root.
POST /txsubmits a transaction (Extended Format)GET /tx/{txid}returns its status, plus themerklePathonce it is minedGET /policyreturns the fee policy
Arcade answers a submission with 202 RECEIVED straight away and broadcasts afterwards. The
status then moves through ACCEPTED_BY_NETWORK and SEEN_ON_NETWORK to MINED after the next
block. libspiffy 4.6.0 follows these steps for you.
Testing from a phone on your LAN? Set HOST_IP=0.0.0.0 in .env and run
./scripts/start.sh again. Arcade (23011), the DataHub (18090) and the wire-protocol port
(18444) then listen on every interface; use your computer's LAN address in the app.
For the full guide (every endpoint, getting coins, following a transaction to its proof, and writing reliable tests) see CONSUMING.md.
Everything listens on 127.0.0.1. Arcade, the DataHub and the wire-protocol port follow
HOST_IP instead.
| Port | Service | What it's for |
|---|---|---|
| 23011 | Arcade | ARC-compatible API (no /v1); health check on 23012 |
| 18090 | DataHub | http://127.0.0.1:18090/api/v1 for blocks, headers, transactions and UTXO status; a block explorer UI at / |
| 19292 | RPC | generate, generatetoaddress, getblockchaininfo, sendrawtransaction (try ./scripts/rpc.sh <method> [params]) |
| 18084 | Propagation | Teranode's own POST /tx and POST /txs |
| 18444 | Wire protocol | Bitcoin P2P (see the user agent note below) |
| 19905 | libp2p | Gossip topics teranode/bitcoin/1.0.0/regtest-{block,subtree,node_status,rejected-tx} |
| 28080 | Merkle Service | /watch and /reprocess, used by Arcade; you rarely need it |
| 25432 / 29092 / 23000 | Postgres / Redpanda / Aerospike | Teranode's data stores |
All scripts live in scripts/.
| Script | What it does |
|---|---|
start.sh |
Starts everything and makes it ready to use |
status.sh |
Shows containers, node state, chain tip and faucet balance |
mine.sh [count] [address] |
Mines blocks now, paying the faucet unless you give an address |
faucet.sh init | address | balance | send <addr> <sats> [--arcade] |
Runs the faucet; --arcade broadcasts through Arcade instead of the node's RPC |
rpc.sh <method> [params...] |
Sends a raw JSON-RPC call |
verify-proof.py <txid> |
Checks Arcade's merklePath for a mined transaction against the block header |
stop.sh |
Stops the stack and keeps the chain |
reset.sh |
Stops the stack and wipes the chain (keeps the faucet key and .env) |
setup.sh |
Writes .env with fresh secrets (start.sh runs it for you when .env is missing) |
These are the things most likely to surprise you, especially coming from arc_regtest.
There is no node wallet. Teranode does not offer sendtoaddress or getnewaddress, so the
faucet (faucet/main.go) does that job. It keeps its key in faucet/key.wif, and the autominer
and mine.sh pay their block rewards to it (miner.env). The faucet finds those coins through
the DataHub, waits the 100 blocks a block reward needs before it can be spent, and signs and
broadcasts its own transactions. It tracks its coins in faucet/state.json.
Wire-protocol clients must identify as BSV. The node only accepts a P2P peer whose user agent
contains BSV or Bitcoin SV (case-sensitive), and bans any other peer for 24 hours.
spiffynode's default /bsv-p2p-dart:0.1.0/ and libspiffy's /LibSpiffy:1.0/ do not pass. The ban
is per IP, and every connection from your machine arrives from the same Docker gateway address,
so one misconfigured client locks out the whole host. To lift it, run
docker restart tnl-legacy (rpc.sh clearbanned does not reach this service).
Fork heights are different. Genesis activates at height 100 and Chronicle at 200. arc_regtest's SV Node activates them at 1 and 15,000.
Arcade charges a fee even though the node doesn't. Teranode here accepts zero-fee
transactions, but Arcade enforces its default of 100 sat/kB (see GET /policy).
Some RPC methods are missing. getblockcount and gettxout are listed but not implemented.
Use getblockchaininfo (which can lag a new block by a few seconds), or the DataHub's
/bestblockheader/json and /utxos/<txid>/json.
A fresh node starts idle. Teranode starts in the IDLE state and mines nothing until it is
switched to RUNNING. start.sh does this for you.
Merkle Service follows Teranode's libp2p gossip (subtrees and blocks) for the transactions Arcade
registers with it, and calls Arcade back with partial proofs. Arcade then builds the full proof
(a BUMP) using block data from the DataHub. Merkle Service stores its data in a merkle database
on the same Postgres.
To connect, Merkle Service dials Teranode's peer service using a peer ID pinned in .env
(p2p_private_key, TERANODE_PEER_ID; setup.sh generates them with tools/p2pkey). Teranode
accepts the connection because p2p_allow_private_ips=true is set, since the Docker network is
private. The DataHub's rate limit for heavy routes is also turned off
(asset_httpHeavyRateLimit=0), as this stack's own tools are its only readers.
Starting from reset.sh and start.sh, a faucet payment submitted through Arcade went from
RECEIVED to ACCEPTED_BY_NETWORK in about 2 seconds, to SEEN_ON_NETWORK about 8 seconds
later, and to MINED about 3 seconds after the next block. Its merklePath computes the merkle
root in the block header.
So far every block has held a single subtree. A block with several subtrees (thousands of transactions) has not been tried yet.
The Docker Compose setup is adapted from
teranode-quickstart (Apache-2.0, see
config/QUICKSTART_LICENSE), cut down to a single regtest node.