Skip to main content

Command Palette

Search for a command to run...

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

Published
11 min read
Interacting with Bitcoin Core: Creating and Broadcasting a Transaction Using RPC
S

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 regtest mode (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_RETURN output (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 regtest mode.

  • 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 listwallets to check if testwallet exists.

  • 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 getnewaddress to 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:

  1. Sends 100 BTC to the recipient.

  2. 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:

  • fundrawtransaction automatically 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?

  • signrawtransactionwithwallet uses the wallet’s private keys to sign the transaction.

  • We check if complete is True to 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?

  • sendrawtransaction submits 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!