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"]
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 want | Command |
|---|---|
| Confirm everything waiting, once | setgenerate 1 |
| Reach N confirmations | setgenerate N |
| Make a block but keep a payment unconfirmed | generateblock '{"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 send | HTTP status | Reply |
|---|---|---|
| Correct call | 200 | {"result":109,"error":null,"id":"t"} |
| Method that does not exist | 404 | {"code":-32601,"message":"Method not found"} |
sendtoaddress to notanaddress | 500 | {"code":-5,"message":"Invalid DIVI address"} |
| No password, or the wrong one | 401 | No 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.
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.0 | IronDivi 0.2.5 | |
|---|---|---|
| Make blocks | setgenerate, generateblock | Refused with -32603. Let a C++ node make the blocks and connect IronDivi to it. |
| Bad address message | Invalid DIVI address | Invalid address. Same code, -5. |
blocknotify, walletnotify | Yes | Not in IronDivi's source. Poll instead. |
8A test run, start to finish
- Build or restore a known chain with make-chain.sh or a snapshot.
- Wait until
getblockcountanswers. - Run your tests. Make blocks with
setgeneratewhenever a test needs a confirmation, and withgenerateblock '{"ignoreMempool":true}'when it needs a payment held back. - Check amounts, confirmations and error codes. Do not assert ids or addresses unless you restored a snapshot: a fresh script run makes new ones.
- Stop the node with
divi-cli -datadir=… stop.
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
| Symptom | Cause | Fix |
|---|---|---|
401: wrong rpcuser or rpcpassword | The client read a different divi.conf from the one the node uses, or the node was not restarted after a change | Pass 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 ECONNREFUSED | The node is not running, or listens on another port | Check with divi-cli -datadir=… getblockcount. If you changed the port, put rpcport= in divi.conf; the scripts read it from there. |
fetch is not defined | Node.js older than 18 | Upgrade 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 runs | The path in -walletnotify is not absolute, or the script is not executable | Use $HOME/… and chmod +x. |
Still stuck? Ask the Divi chatbot, or see all tutorials.