Interacting with Bitcoin Core: Creating and Broadcasting a Transaction Using RPC

Sharing my learnings on Full Stack, Web3 and DevOps with the Community.
Introduction
Have you ever wondered how Bitcoin transactions are created under the hood? When you send Bitcoin using a wallet, there’s a lot happening behind the scenes—address generation, transaction signing, fee calculations, and broadcasting to the network. But what if you could do all of this yourself, programmatically, without relying on a third-party wallet?
That’s where Bitcoin Core’s RPC (Remote Procedure Call) interface comes in. It allows you to interact with a Bitcoin node directly—creating wallets, generating addresses, mining blocks, and even crafting raw transactions. In this blog, we’ll take a hands-on approach to building and broadcasting a Bitcoin transaction using only Bitcoin Core and Python.
By the end of this guide, you’ll learn how to:
Set up a Bitcoin Core node in
regtestmode (a private Bitcoin network for testing).Create a wallet and generate a Bitcoin address.
Mine blocks to get spendable BTC.
Create a transaction that includes an
OP_RETURNoutput (to embed a message in the blockchain).Set a custom transaction fee and broadcast the transaction.
Confirm the transaction by mining another block.
No previous experience with Bitcoin Core or RPC? No worries! I’ll break everything down in simple terms, and you’ll be running Bitcoin transactions from your own node in no time. Let’s dive in!
Bitcoin Jargon Dictionary
Before we get started, here’s a quick rundown of some key Bitcoin terms you’ll encounter in this blog:
Bitcoin Core: The open-source software that runs a Bitcoin node, allowing you to interact with the Bitcoin network.
RPC (Remote Procedure Call): A way to send commands to your Bitcoin node programmatically.
Regtest (Regression Test Mode): A private Bitcoin network used for testing, where you can mine blocks instantly.
Wallet: A digital storage for your Bitcoin, including private keys used to sign transactions.
Address: A unique identifier where Bitcoin can be sent.
Mining: The process of validating and adding transactions to the blockchain.
OP_RETURN: A special type of Bitcoin transaction output that allows storing small pieces of data on the blockchain.
Transaction Fee: The fee paid to miners for processing and confirming a transaction.
TXID (Transaction ID): A unique identifier for a Bitcoin transaction.
Now that we’re familiar with these terms, let’s move on to setting up our Bitcoin Core node!
Setting Up Bitcoin Core in Regtest Mode
Before we start creating transactions, we need a Bitcoin node running in regtest mode. Regtest is a private testing environment where you can generate blocks instantly, making it perfect for development. We’ll set up Bitcoin Core using Docker, so you don’t have to install it manually.
Step 1: Install Docker and Docker Compose
If you haven’t already installed Docker, you can get it from Docker’s official website. Once installed, ensure that both docker and docker-compose are available in your terminal:
$ docker --version
$ docker-compose --version
If both commands return version numbers, you’re good to go!
Step 2: Create a docker-compose.yml File
We’ll use Docker Compose to run Bitcoin Core with the correct configurations. Create a new directory and inside it, create a file named docker-compose.yml with the following content:
services:
bitcoin:
image: btcpayserver/bitcoin:24.0.1-1
environment:
BITCOIN_NETWORK: regtest
BITCOIN_EXTRA_ARGS: |
server=1
rest=1
rpcbind=0.0.0.0:18443
rpcallowip=0.0.0.0/0
rpcauth=alice:88cae77e34048eff8b9f0be35527dd91$$d5c4e7ff4dfe771808e9c00a1393b90d498f54dcab0ee74a2d77bd01230cd4cc
debug=1
logips=1
logtimemicros=1
blockmintxfee=0
deprecatedrpc=signrawtransaction
listenonion=0
fallbackfee=0.00001
txindex=1
ports:
- "18443:18443"
This configuration does the following:
Runs Bitcoin Core in
regtestmode.Enables the RPC server so we can interact with it.
Exposes the RPC port (
18443).Configures authentication for RPC access.
Step 3: Start the Bitcoin Node
Now, navigate to the directory where your docker-compose.yml file is located and run:
$ docker-compose up -d
This will download and start the Bitcoin Core container in the background.
Step 4: Verify the Node is Running
To check if your node is running, use:
$ docker ps
You should see a running container named bitcoin.
I'll be using the CLI to walk through each step. If you'd rather automate the process, a Python script will be provided at the end, allowing you to achieve the same results with a single command.
To interact with your node, open a shell inside the container:
$ docker exec -it $(docker ps -q -f "name=bitcoin") bitcoin-cli -regtest getblockchaininfo
If everything is working, you’ll see blockchain information, confirming that your node is running in regtest mode.
Now that our Bitcoin node is up and running, let’s create a wallet and generate some Bitcoin! 🚀
Creating a Wallet and Generating Bitcoin Addresses
Now that our Bitcoin node is up and running, we need to create a wallet and generate an address to receive Bitcoin.
Step 1: Create a New Wallet
Run the following command inside the Bitcoin container:
$ docker exec -it $(docker ps -q -f "name=bitcoin") bitcoin-cli -regtest createwallet "testwallet"
This will create a new wallet named testwallet. If the wallet already exists, you can load it using:
$ docker exec -it $(docker ps -q -f "name=bitcoin") bitcoin-cli -regtest loadwallet "testwallet"
Step 2: Generate a New Bitcoin Address
Once the wallet is loaded, generate a new Bitcoin address:
$ docker exec -it $(docker ps -q -f "name=bitcoin") bitcoin-cli -regtest getnewaddress
This address will be used to receive mined Bitcoin in the next step.
Step 3: Mine Blocks to Fund the Wallet
To get some Bitcoin in regtest mode, we need to mine blocks. Run:
$ docker exec -it $(docker ps -q -f "name=bitcoin") bitcoin-cli -regtest generatetoaddress 101 <YOUR_GENERATED_ADDRESS>
Replace <YOUR_GENERATED_ADDRESS> with the address you generated earlier.
This will mine 101 blocks, rewarding you with Bitcoin that you can now spend.
In the next section, we’ll move on to creating and broadcasting a Bitcoin transaction! 🚀
Creating and Broadcasting a Bitcoin Transaction
Now that we have a funded wallet, let's create and broadcast a Bitcoin transaction step by step.
Step 1: Check Wallet Balance
Before spending Bitcoin, verify your balance:
$ docker exec -it $(docker ps -q -f "name=bitcoin") bitcoin-cli -regtest getbalance
You should see a balance from the mined blocks.
Step 2: Select a UTXO (Unspent Transaction Output)
To create a transaction, we need to choose an available UTXO:
$ docker exec -it $(docker ps -q -f "name=bitcoin") bitcoin-cli -regtest listunspent
This will return details of unspent outputs. Note the txid and vout of the output you want to use.
Step 3: Create a Raw Transaction
Now, create a transaction sending Bitcoin to a new address. First, generate a recipient address:
$ docker exec -it $(docker ps -q -f "name=bitcoin") bitcoin-cli -regtest getnewaddress
Then, construct the raw transaction (replace <TXID> and <VOUT> with the values from listunspent):
$ docker exec -it $(docker ps -q -f "name=bitcoin") bitcoin-cli -regtest createrawtransaction '[{"txid":"<TXID>","vout":<VOUT>}]' '{"<RECIPIENT_ADDRESS>":0.5}'
This creates an unsigned transaction sending 0.5 BTC to the new address.
Step 4: Sign the Transaction
Now, sign the raw transaction:
$ docker exec -it $(docker ps -q -f "name=bitcoin") bitcoin-cli -regtest signrawtransactionwithwallet "<RAW_TX>"
Replace <RAW_TX> with the output from the previous step.
Step 5: Broadcast the Transaction
Finally, send the signed transaction:
$ docker exec -it $(docker ps -q -f "name=bitcoin") bitcoin-cli -regtest sendrawtransaction "<SIGNED_TX>"
If successful, this returns a transaction ID (txid), meaning your transaction is now in the mempool.
Step 6: Confirm the Transaction
Mine a block to confirm the transaction:
$ docker exec -it $(docker ps -q -f "name=bitcoin") bitcoin-cli -regtest generatetoaddress 1 <ANY_ADDRESS>
Check if your transaction is confirmed:
$ docker exec -it $(docker ps -q -f "name=bitcoin") bitcoin-cli -regtest gettransaction "<TXID>"
If the confirmations field is 1 or higher, your transaction is confirmed!
Automating with Python: Creating and Broadcasting a Transaction
While using CLI commands is great for understanding how things work, running multiple commands manually can be tedious. Let’s automate the entire process with Python using the requests library to interact with Bitcoin Core's RPC.
Step 1: Setting Up the RPC Connection
We’ll start by defining a helper function to send RPC requests.
pythonCopyEditimport requests
import json
RPC_USER = "alice"
RPC_PASSWORD = "password"
RPC_PORT = 18443
RPC_URL = f"http://{RPC_USER}:{RPC_PASSWORD}@localhost:{RPC_PORT}"
def rpc_call(method, params=[]):
"""Send an RPC request to the Bitcoin node."""
payload = json.dumps({"jsonrpc": "1.0", "id": "curltest", "method": method, "params": params})
headers = {"content-type": "application/json"}
response = requests.post(RPC_URL, headers=headers, data=payload)
return response.json()
👉 What’s Happening Here?
We define
rpc_call()to send JSON-RPC requests to our Bitcoin node.The function takes an RPC method and optional parameters.
It sends a POST request and returns the response.
Step 2: Creating and Loading the Wallet
Now, let’s check if our wallet exists. If not, we’ll create one.
pythonCopyEditwallet_name = "testwallet"
# Check if wallet exists
wallets = rpc_call("listwallets")["result"]
if wallet_name not in wallets:
rpc_call("createwallet", [wallet_name])
# Load the wallet
rpc_call("loadwallet", [wallet_name])
👉 Key Points:
We use
listwalletsto check iftestwalletexists.If the wallet isn’t found, we create it using
createwallet.Finally, we ensure the wallet is loaded.
Step 3: Generating a New Address and Mining Blocks
Once the wallet is ready, we need to generate a new address and mine some blocks to fund it.
pythonCopyEdit# Generate a new receiving address
new_address = rpc_call("getnewaddress")["result"]
print(f"Generated Address: {new_address}")
# Mine 300 blocks to this address
rpc_call("generatetoaddress", [300, new_address])
print("Mined 300 blocks to fund the wallet.")
👉 What’s Happening Here?
We use
getnewaddressto create a new Bitcoin address.Then, we mine 300 blocks using
generatetoaddress, which rewards us with Bitcoin for testing.
Step 4: Checking Wallet Balance
Before creating a transaction, let’s ensure the wallet has enough funds.
pythonCopyEditbalance = rpc_call("getbalance")["result"]
print(f"Wallet Balance: {balance} BTC")
👉 Why This Matters?
If the balance is too low, we can mine more blocks.
It ensures our transaction doesn’t fail due to insufficient funds.
Step 5: Creating the Transaction
Now that our wallet is funded, let’s create a transaction that:
Sends 100 BTC to the recipient.
Includes an OP_RETURN output with the message "We are all Satoshi!!".
pythonCopyEditrecipient_address = "bcrt1qq2yshcmzdlznnpxx258xswqlmqcxjs4dssfxt2"
op_return_message = "We are all Satoshi!!"
op_return_hex = op_return_message.encode().hex() # Convert message to hex
# Define the transaction outputs
outputs = [
{recipient_address: 100.0}, # Sending 100 BTC
{"data": op_return_hex} # OP_RETURN output
]
# Create a raw transaction
raw_tx = rpc_call("createrawtransaction", [[], outputs])["result"]
print(f"Raw Transaction Created: {raw_tx}")
👉 What’s Happening Here?
We define the recipient and encode our OP_RETURN message into hexadecimal.
The transaction has two outputs:
100 BTC to the recipient.
OP_RETURN output containing the message.
We create a raw transaction (an unsigned transaction).
Step 6: Funding the Transaction
Before signing, we need to fund the transaction by selecting UTXOs and setting a fee rate of 21 sats/vB.
pythonCopyEditfunded_tx = rpc_call("fundrawtransaction", [raw_tx, {"fee_rate": 21, "changePosition": 1}])["result"]
raw_tx = funded_tx["hex"]
print("Transaction funded.")
👉 Key Points:
fundrawtransactionautomatically selects UTXOs to cover the amount.We set a fee rate of 21 sats/vB.
It returns a funded transaction hex.
Step 7: Signing the Transaction
Now that our transaction is funded, we need to sign it with our wallet’s private keys.
pythonCopyEditsigned_tx = rpc_call("signrawtransactionwithwallet", [raw_tx])["result"]
# Ensure signing is complete
if not signed_tx["complete"]:
raise Exception("Transaction signing failed")
print("Transaction signed successfully.")
👉 What’s Happening Here?
signrawtransactionwithwalletuses the wallet’s private keys to sign the transaction.We check if
completeisTrueto confirm successful signing.
Step 8: Broadcasting the Transaction
Now, let’s broadcast the signed transaction to the Bitcoin network.
pythonCopyEdittxid = rpc_call("sendrawtransaction", [signed_tx["hex"]])["result"]
print(f"Transaction broadcasted successfully, TXID: {txid}")
👉 What’s Happening Here?
sendrawtransactionsubmits the signed transaction to the network.It returns the transaction ID (TXID).
Step 9: Mining a Block to Confirm the Transaction
Since we’re in regtest mode, transactions don’t confirm automatically. Let’s mine a block to include our transaction.
pythonCopyEditrpc_call("generatetoaddress", [1, new_address])
print("Mined 1 block to confirm the transaction.")
👉 Why This Step?
It ensures our transaction is confirmed on the blockchain.
In a real-world scenario, we’d wait for miners to confirm it.
Step 10: Saving the TXID to out.txt
Finally, let’s store the transaction ID in a file.
pythonCopyEditwith open("out.txt", "w") as f:
f.write(txid)
print("Transaction ID saved to out.txt.")
👉 Final Step!
- This allows easy retrieval of the transaction ID.
🎉 Congratulations! You’ve successfully created and broadcasted a Bitcoin transaction using RPC 🚀.
Next, let’s wrap up the blog with some final thoughts.
Conclusion
We’ve successfully interacted with a Bitcoin Core node using RPC, created a wallet, generated an address, mined blocks, and sent a transaction with an OP_RETURN output—all using both the command line and a Python script.
Through this hands-on experience, you now understand:
✅ How to set up and interact with a Bitcoin node
✅ How to create and fund transactions
✅ How to broadcast transactions and confirm them on the blockchain
This is just the beginning! With Bitcoin Core’s powerful RPC interface, you can build wallets, analyze blockchain data, and even contribute to Bitcoin’s open-source ecosystem.
💡 Next Steps:
Try modifying the script to send different amounts or messages in
OP_RETURN.Explore more RPC commands from the Bitcoin Core API.
Join Bitcoin developer communities and start contributing!
🚀 Bitcoin is programmable money—now you know how to program it!



