Guide

Test a Bitcoin transaction before broadcast with Bitcoin Core

Build a signed transaction on regtest, ask Bitcoin Core 31.2 whether its current mempool would accept it, and prove that the test did not broadcast the transaction.

7 min readTransactions
Test a Bitcoin transaction before broadcast with Bitcoin Core

What you will do

testmempoolaccept asks a Bitcoin Core node whether one or more signed raw transactions would pass its current consensus and mempool policy checks. It returns a structured result without adding the transaction to the mempool and without broadcasting it.

This guide creates a disposable regtest wallet, mines synthetic blocks, builds and signs one transaction, and tests it twice. The first call should return allowed: true. The second sets an intentionally tiny maximum fee rate and should return max-fee-exceeded. A final getrawmempool call should still return an empty array.

Nothing here uses mainnet, testnet coins, a production wallet, peers or private material that matters. Do not substitute a real wallet or a transaction containing real funds while learning the procedure.

Before you start

You need Bitcoin Core 31.2, jq, a POSIX shell and about 2 GB of free disk space. Use the binaries and signatures linked from the official Bitcoin Core 31.2 release page. Verify the downloaded artifact before running it.

The examples expect bitcoind and bitcoin-cli on your PATH. They start a new node in a directory under the current folder. Read every command before running it.

Bitcoin Core 31.2 was released on 28 October 2026. Its released testmempoolaccept implementation documents these boundaries:

  • the input is an array of signed, serialized transactions in hexadecimal
  • a single transaction is checked against the node's current chain and mempool
  • allowed: true includes virtual size and fee information
  • allowed: false includes a rejection reason and sometimes details
  • maxfeerate is expressed in BTC per 1,000 virtual bytes
  • testing does not reserve inputs or promise later acceptance

This publication run did not have a Bitcoin Core binary available. The shell was checked for syntax and the commands were compared with the 31.2 RPC source and functional tests, but the regtest procedure below was not executed here. Treat the stated output shapes as expected results to verify on your own disposable node, not as a claim about a run performed for you.

Start an isolated regtest node

Create a new working folder and a data directory. The guard refuses to reuse an existing directory.

mkdir testmempoolaccept-practice
cd testmempoolaccept-practice

DATADIR="$PWD/regtest-data"
if [ -e "$DATADIR" ]; then
  echo "Refusing to reuse $DATADIR" >&2
  exit 1
fi
mkdir "$DATADIR"

bitcoind -regtest -datadir="$DATADIR" -server=1 -daemon -fallbackfee=0.0002
bitcoin-cli -regtest -datadir="$DATADIR" -rpcwait getblockchaininfo >/dev/null

Regtest is a private local chain. It does not discover public peers or use real bitcoin. -rpcwait waits until the RPC server is ready.

Create a descriptor wallet, obtain a mining address and generate 101 blocks. The extra block makes the first coinbase output mature and spendable.

bitcoin-cli -regtest -datadir="$DATADIR" createwallet "preflight"

MINER=$(bitcoin-cli -regtest -datadir="$DATADIR" \
  -rpcwallet=preflight getnewaddress "mining" bech32)

bitcoin-cli -regtest -datadir="$DATADIR" \
  generatetoaddress 101 "$MINER" >/dev/null

Expected result: the commands finish without an RPC error. This is synthetic regtest history. No network funds are created.

Build and sign without broadcasting

Create another address in the same disposable wallet. Then build a funded PSBT, let the wallet sign it and finalize it to raw transaction hex.

DEST=$(bitcoin-cli -regtest -datadir="$DATADIR" \
  -rpcwallet=preflight getnewaddress "destination" bech32)

PSBT=$(bitcoin-cli -regtest -datadir="$DATADIR" \
  -rpcwallet=preflight walletcreatefundedpsbt \
  '[]' "{\"$DEST\":1.00000000}" 0 '{"add_inputs":true}' true \
  | jq -er '.psbt')

SIGNED=$(bitcoin-cli -regtest -datadir="$DATADIR" \
  -rpcwallet=preflight walletprocesspsbt "$PSBT" \
  | jq -er '.psbt')

HEX=$(bitcoin-cli -regtest -datadir="$DATADIR" \
  -rpcwallet=preflight finalizepsbt "$SIGNED" \
  | jq -er '.hex')

test -n "$HEX"

walletcreatefundedpsbt selects a mature regtest coin, adds change and estimates a fee. walletprocesspsbt signs with the disposable wallet. finalizepsbt returns the serialized transaction. None of these commands sends it to the mempool.

Do not paste real raw transaction hex into a public service. A signed transaction may be broadcast by anyone who obtains it, and its inputs and outputs can reveal wallet activity.

Ask the node for a preflight result

Pass the signed hex as the only item in the JSON array:

bitcoin-cli -regtest -datadir="$DATADIR" \
  testmempoolaccept "[\"$HEX\"]" | tee allowed.json

jq -e '
  length == 1 and
  .[0].allowed == true and
  .[0].vsize > 0 and
  .[0].fees.base > 0
' allowed.json

The result should contain txid, wtxid, allowed, vsize and a fees object. The exact identifiers, size and fee depend on the wallet's selected input and change, so do not copy fixed values from another run.

Now check that the RPC did not insert the transaction:

bitcoin-cli -regtest -datadir="$DATADIR" getrawmempool

Expected result:

[]

An empty result is the important separation between testing and submitting. Do not call sendrawtransaction in this exercise.

Run a controlled negative test

The second argument to testmempoolaccept is the maximum fee rate in BTC per 1,000 virtual bytes. Use an intentionally tiny value:

bitcoin-cli -regtest -datadir="$DATADIR" \
  testmempoolaccept "[\"$HEX\"]" 0.00000001 \
  | tee maxfee.json

jq -e '
  length == 1 and
  .[0].allowed == false and
  .[0]["reject-reason"] == "max-fee-exceeded"
' maxfee.json

This does not mean the transaction violates Bitcoin consensus. It means the fee exceeds the caller's temporary safety ceiling. Bitcoin Core's 31.2 functional test uses the same class of negative control and separately confirms an allowed result when the ceiling is high enough.

Test the mempool again. It should remain empty.

bitcoin-cli -regtest -datadir="$DATADIR" getrawmempool

Read rejection results carefully

allowed: false is a local result at one moment. Common reasons include missing or already-spent inputs, a fee below the node's current floor, a conflict with a mempool transaction, a non-standard script or the caller's maxfeerate limit.

Malformed hex is different. If Bitcoin Core cannot deserialize the transaction, the RPC itself returns an error instead of an item with allowed: false. Fix the serialization or signing step before interpreting policy.

For a package, parents must come before children. If one transaction fails, later entries may contain only identifiers because they were not fully validated. A package-level problem can appear as package-error rather than an individual rejection.

Do not display allowed: true to users as a guarantee. Between the test and a later broadcast:

  • another transaction can spend the same input
  • the node's mempool contents and minimum fee can change
  • a different node can use different policy or have a different view of the chain
  • reorganization or package context can change the result
  • miners choose transactions independently

The RPC also does not prove that the destination, amount or fee matches the user's intent. Review those before signing, not only after producing raw hex.

Troubleshooting

bitcoin-cli cannot connect. Confirm that every command uses the same absolute DATADIR. Inspect regtest-data/regtest/debug.log. Do not switch to a production data directory.

The wallet has insufficient funds. Confirm that 101 regtest blocks were generated to an address from the same wallet. Coinbase outputs need 100 confirmations before spending.

finalizepsbt has no hex. Inspect the complete result without sending it anywhere. If complete is false, the wallet did not provide every required signature. In this exercise, recreate the disposable wallet rather than importing keys.

The first test returns missing-inputs. Make sure the transaction and the node share the same regtest chain and data directory. Raw hex built against another chain will refer to outputs this node does not know.

The first test returns a fee-policy rejection. Inspect getmempoolinfo and the fees object. Do not solve an unexpected fee by setting maxfeerate to zero without understanding the amount.

The negative test is allowed. Stop and inspect maxfee.json, the Core version and the actual fee rate. Do not weaken the assertion just to make it pass.

Clean up

Stop the disposable node before removing its directory:

bitcoin-cli -regtest -datadir="$DATADIR" stop

After the process exits, the entire testmempoolaccept-practice folder can be deleted. It contains only synthetic blocks, a disposable wallet and test files.

What was and was not verified

The command sequence was checked with bash -n. Its RPC names, arguments and result fields were compared with the Bitcoin Core 31.2 source and released functional tests. The illustration was rendered from the hand-authored SVG and inspected as a 1200 by 630 WebP.

No Bitcoin Core binary was available in this publication workspace, so the regtest commands were not executed here. No wallet, node, key, signed transaction, network or funds were used. Run the procedure only in a fresh regtest directory and treat any different output as something to investigate, not a result to overwrite.

Sources

Newsletter

Bitcoin, without the noise

What happened in Bitcoin, what it actually changes, and the sources so you can check us. One issue at a time, straight to your inbox.

  • One email per issue, never a drip campaign
  • No tracking pixels and no shared addresses
  • Unsubscribe from any issue in one click

Get the next issue

One email per issue, no tracking pixels, and unsubscribe from any of them. We do not share your address. Privacy policy