Tutorial · Regtest

Test Your App on Regtest

Your wallet, shop or bot talks to a Divi node over JSON-RPC. On regtest you can make that node do anything on demand: pay, confirm, hold a payment back, refuse a bad request. This page shows working clients in curl, Python and JavaScript, how to control confirmations, how to test the error paths, and how to react to new blocks and payments.

About 20 minutes · Checked 2026-10-21
Before you start

You need a running C++ regtest node with coins, as in start your first regtest node: data folder ~/divi-regtest, RPC port 51475, at least 101 blocks. For a node that starts from the same state every time, use the script in a chain you can rebuild. The output below was captured on a test machine whose nodes used other ports; heights, ids and addresses on yours will differ.

1One call with curl

Every RPC call is an HTTP POST with a small JSON body and the RPC user and password from divi.conf. Typing the password on the curl command line would leave it in your shell history and in the process list, so put it in a curl config file that only you can read:

( umask 077; printf 'user = "regtest:%s"\n' \
  "$(grep '^rpcpassword=' ~/divi-regtest/divi.conf | cut -d= -f2)" \
  > ~/divi-regtest/curl.cfg )

Then ask for the block count:

curl -s -K ~/divi-regtest/curl.cfg -H 'Content-Type: application/json' \
  --data '{"jsonrpc":"1.0","id":"t","method":"getblockcount","params":[]}' \
  http://127.0.0.1:51475/
{"result":109,"error":null,"id":"t"}

Every reply has the same three fields. result holds the answer, error is null when the call worked, and id repeats whatever you sent so you can match replies to requests. Any command divi-cli knows works the same way: put its name in method and its arguments, in order, in params.

2A Python client

Download regtest_rpc.py. It uses only Python's standard library, reads the port, user and password from divi.conf so no secret is on the command line, and runs a small test: send 12.5 DIVI to a new address, check it has 0 confirmations, make one block, check it has 1, then check that a bad address is refused.

python3 regtest_rpc.py ~/divi-regtest
height before: 111
sent 12.5 to y4PwVcXWsPjNKDrUYAucNxQKDihoDhyMN3 txid afafa567ae93de67767981e92883a5e755462ea1ee365cc4c60c9d1b709d6354
confirmations before block: 0
confirmations after block:  1
height after:  112
bad address refused: {'code': -5, 'message': 'Invalid DIVI address'}

The heart of it is one function. Copy it into your own tests:

def rpc(method, *params):
    body = json.dumps({"jsonrpc": "1.0", "id": "py", "method": method, "params": list(params)}).encode()
    req = urllib.request.Request(URL, body, {"Authorization": "Basic " + AUTH, "Content-Type": "application/json"})
    try:
        with urllib.request.urlopen(req) as r:
            reply = json.load(r)
    except urllib.error.HTTPError as e:     # divid answers errors with HTTP 404/500 and a JSON body
        if e.code == 401:
            raise RuntimeError("401: wrong rpcuser or rpcpassword") from None
        reply = json.load(e)
    if reply.get("error"):
        raise RuntimeError(reply["error"])
    return reply["result"]
Errors arrive as HTTP errors

When a call fails, divid sends the JSON error with HTTP status 500, or 404 for an unknown method. Python's urlopen turns that into an exception, and the JSON is easy to lose. The except branch above reads it back, so your test sees the real code and message.

3A JavaScript client

Download regtest_rpc.mjs. It needs Node.js 18 or later, for the built-in fetch, and no packages. Give it a data folder for a C++ node:

node regtest_rpc.mjs ~/divi-regtest
chain regtest height 112 tip 6b645659f377cd0fe398f38c980b53f4f6d0e44ea0070f8a2401a29bee58e9df
tip has 3 transactions, time 1792665968
new address y6ACo3MCBXM1CEd19we3UdUndJLsPHm2nP
made block e388bbe7a0b18a24213fb24ff0714ff80b8ffbc2454b92785d0b88de0d7a1268
bad address refused: {"code":-5,"message":"Invalid DIVI address"}

The same function in JavaScript. Unlike Python, fetch does not throw on HTTP 500, so the JSON error body is read the normal way:

async function rpc(method, ...params) {
  const r = await fetch(url, { method: "POST", headers, body: JSON.stringify({ jsonrpc: "1.0", id: "js", method, params }) });
  if (r.status === 401) throw new Error("401: wrong rpcuser or rpcpassword");
  const reply = await r.json();
  if (reply.error) throw new Error(JSON.stringify(reply.error));
  return reply.result;
}

4Confirmations on demand

On mainnet you wait about a minute per block. On regtest you decide. These are the three moves an app test needs:

You wantCommand
Confirm everything waiting, oncesetgenerate 1
Reach N confirmationssetgenerate N
Make a block but keep a payment unconfirmedgenerateblock '{"ignoreMempool":true}'

The third one is for testing "pending" screens. Blocks keep arriving, but your payment stays in the mempool, the list of transactions waiting for a block:

C="divi-cli -datadir=$HOME/divi-regtest"
T=$($C sendtoaddress $($C getnewaddress) 7)
$C getrawmempool
[
    "3b21520893d5ca77d2d675a12e89d963457ebc69041caeeeff33c6750f3576d8"
]
$C generateblock '{"ignoreMempool":true}'
fca8a39230fa65a852088912d6282a67043bc857ba1e56a86f79a66ec0f8c0a0
$C gettransaction $T | grep -m1 confirmations
    "confirmations" : 0,
$C getrawmempool
[
    "3b21520893d5ca77d2d675a12e89d963457ebc69041caeeeff33c6750f3576d8"
]
$C setgenerate 1 >/dev/null
$C gettransaction $T | grep -m1 confirmations
    "confirmations" : 1,
$C getmempoolinfo
{
    "size" : 0,
    "bytes" : 0
}

The height went up by one with generateblock while the payment stayed at 0 confirmations. The next ordinary block picked it up. generateblock cannot set a block's time, so tests that depend on the clock cannot be sped up this way.

5Test the error paths

An app is only as good as what it does when the node says no. These replies came from a C++ regtest node; make your tests expect them:

What you sendHTTP statusReply
Correct call200{"result":109,"error":null,"id":"t"}
Method that does not exist404{"code":-32601,"message":"Method not found"}
sendtoaddress to notanaddress500{"code":-5,"message":"Invalid DIVI address"}
No password, or the wrong one401No JSON at all

For a "not enough money" test, send more than getbalance shows; where regtest coins come from lists that error verbatim. Check the error code, not the wording: section 7 shows the wording changing between node programs while the code stays the same.

6React to blocks and payments

Polling getblockcount works, but divid can also run a program of yours each time something happens. %s in the command is replaced by the block hash or transaction id. A tiny script that logs each call:

cat > ~/notify.sh <<'END'
#!/bin/sh
echo "$(date +%T) $1 $2" >> "$HOME/divi-regtest/notify.log"
END
chmod +x ~/notify.sh

Stop the node, then start it with both hooks:

divid -datadir=$HOME/divi-regtest -daemon \
  "-blocknotify=$HOME/notify.sh block %s" \
  "-walletnotify=$HOME/notify.sh wallet %s"

Send a payment, make one block, and read ~/divi-regtest/notify.log:

10:46:13 wallet 53e1cb793d24718945c6ce6bdba42f15eb16e74ef5a117a2a1341008cb877107
10:46:14 wallet f98e1ced82e2e1b5cfd5fbc3f191fa59a64faa7ce93dac60df03fc1cbb2c8445
10:46:14 wallet f98e1ced82e2e1b5cfd5fbc3f191fa59a64faa7ce93dac60df03fc1cbb2c8445
10:46:15 wallet fc833e51acc3d68d3ae00a1da2c3347244a4df2f5e8b87bd07df60068407addd
10:46:15 block 251d14ddc13db0961f0b27d5fa439283fa6dc5ad15a004606faebc5e4f7c68ac
10:46:15 wallet f98e1ced82e2e1b5cfd5fbc3f191fa59a64faa7ce93dac60df03fc1cbb2c8445

The payment f98e1ced… was reported three times: twice around the send and once more when the block confirmed it. fc833e51… is the new block's staking reward, which also lands in the wallet. The first line came from a wallet transaction the node reported while starting up.

One id, many calls

walletnotify runs when a wallet transaction is new or updated, so the same id comes more than once. Treat it as "go and look": call gettransaction on the id, act on its confirmations, and make your handler safe to run twice.

7If your app talks to IronDivi

IronDivi, the Rust node, answers the same JSON-RPC calls, so the same clients work. Started as in two regtest nodes, without an RPC password, it needs no login: give the JavaScript client its port instead of a folder.

node regtest_rpc.mjs 51485
chain regtest height 155 tip a1575991aed2a57705b8b93eaed4c1596778f3e7c23050a79c409040f18fbd08
tip has 3 transactions, time 1792613126
new address y3hiaFBMZEiMUWc7CGSovzEuP8fwWKjcM2
setgenerate refused: {"code":-32603,"message":"setgenerate is not yet implemented - requires full mining subsystem"}
bad address refused: {"code":-5,"message":"Invalid address"}

Three differences to plan for, all seen with IronDivi 0.2.5:

C++ divid 3.0.0IronDivi 0.2.5
Make blockssetgenerate, generateblockRefused with -32603. Let a C++ node make the blocks and connect IronDivi to it.
Bad address messageInvalid DIVI addressInvalid address. Same code, -5.
blocknotify, walletnotifyYesNot in IronDivi's source. Poll instead.

8A test run, start to finish

  1. Build or restore a known chain with make-chain.sh or a snapshot.
  2. Wait until getblockcount answers.
  3. Run your tests. Make blocks with setgenerate whenever a test needs a confirmation, and with generateblock '{"ignoreMempool":true}' when it needs a payment held back.
  4. Check amounts, confirmations and error codes. Do not assert ids or addresses unless you restored a snapshot: a fresh script run makes new ones.
  5. Stop the node with divi-cli -datadir=… stop.
Regtest only

The scripts on this page send coins without asking. Point them only at a node with regtest=1 in its divi.conf, never at a wallet holding real DIVI.

If something goes wrong

SymptomCauseFix
401: wrong rpcuser or rpcpasswordThe client read a different divi.conf from the one the node uses, or the node was not restarted after a changePass the node's data folder to the script. Restart the node after editing divi.conf.
Python: <urlopen error [Errno 111] Connection refused>; Node.js: fetch failed with ECONNREFUSEDThe node is not running, or listens on another portCheck with divi-cli -datadir=… getblockcount. If you changed the port, put rpcport= in divi.conf; the scripts read it from there.
fetch is not definedNode.js older than 18Upgrade Node.js, or use the Python script.
Insufficient funds: …(total requested) is greater than …(total available)The test sends more than the wallet can spend. On a young chain, block rewards are not spendable until they have 21 confirmations.setgenerate 101 on a fresh chain, as in where regtest coins come from.
Hook script never runsThe path in -walletnotify is not absolute, or the script is not executableUse $HOME/… and chmod +x.

Still stuck? Ask the Divi chatbot, or see all tutorials.