February 15, 2026

Sending Bitcoin (BTC) with Ruby – Ace Subido

Sending Bitcoin (BTC) with Ruby – Ace Subido

Sending Bitcoin (BTC) with Ruby – Ace Subido

Sending Bitcoin (BTC) with Ruby – Ace Subido

That was me btw, well, I’ve written down some things that I’ve learned along the way in the most simplified way I can think of: So, here’s a step by step guide in sending Bitcoin with ruby by leveraging BTCd’s JSON RPC endpoint.

Security is another topic worth another post (limit the surface of attack, pod/vm-level restrictions, network-level restraints, multiple BTCd’s, not keeping large amounts of bitcoin in your hot wallet, etc.) but for now, let’s send some BTCs ?

You have 5BTC in your wallet, it’s split up in 4 different addresses.

You want to send 0.01BTC to someone, their address is 1some1 your input would just be any of those addresses, let’s say we want to use 3addr:

What happens to the 0.49BTC? Well you’d need to send it back to yourself. Best to generate a new address, lets call it 5addr so the transaction would look like:

The transaction fee is the difference between the sum of your inputs and sum of your output amounts. Let’s say the transaction fees would be 0.000178BTC

If you still didn’t get it, it’s okay! If you understand concepts better when you’re writing the code and tests, then it’ll probably click as you go along ?

First we’ll want to set what type of fees are “fast”, so that we can use the right number when creating the transaction. We can stuff this code inside some simple service class.

module Bitcoin (BTC)zzz
class GetFees

AVE_TX_BYTES = 250.0

def self.call
client = Bitcoin (BTC)er.new(<your creds here>)
# whats the fee for 3 blocks?
response = client.request("estimatesmartfee", 3)
if response["errors"].any?
# ... do something
end

# estimatesmartfee returns fees per kB (1000 bytes),
# we only want the fee for 250bytes (average tx size).
# you can also compute for tx size if you want to.
fee = response["feerate"].to_d * (AVE_TX_SIZE / 1000.0)
fee
end

end
end

Can’t we just use sendtoaddress?

Yes you can! That’s definitely the easiest way to do this, but if you want more control on fees, outputs being spent; splitting up what sendtoaddress does would be one step to that direction (a.k.a. small refactors using the Ship of Theseus method.), and we get to learn along the way too!

So here’s the steps to mimic sendtoaddress similarly

Getting your unspent money (What can I use for my inputs?)

To build the transaction you’ll need to get “spendable outputs”. To simplify the explanation, this is where the BTC will come from, a list of unspent addresses in the BTCd wallet.

unspent = client.request("listunspent")unspent.inspect
#=> [
,
,
...
]

Filter what you’ll use (What can I use for my inputs?)

Presented with this list, just grab what’s “spendable”. It means that this transaction can be used by BTCd for sending (underneath, all that means is that the BTCd has the keys to use that address).

# what you want your recipient to actually receive
receivable_amount = 0.01
# sample fee from Bitcoin (BTC)zzz::GetFees.()
fee = 0.000178
# filter all spendable
spendable = unspent.map do |output|
output if output["spendable"]
end.compact

If you’re sending 0.01BTC + 0.000178BTC, you won’t need all the “spendable outputs”, so just get the right amount of outputs.

total_usable = 0# total amount you'll spend: 0.01 + 0.000178
sending_amount = receivable_amount + fee
# just get the right amount of outputs
usable = spendable.map do |output|
if total_usable < sending_amount
total_usable += output["amount"]
output
end
end.compact
# based on the unspent example, 'usable' array will only contain the 1addr hash by now. You're only sending 0.01BTC + 0.000178BTC anyway. 1addr contains 2BTC

Get a change address (Where will I send it? My outputs?)

Remember what I said earlier about outputs?

You’ll need to send the difference back to yourself. More on that later.

Of course you only want to send 0.01BTC + 0.000178BTC (fees). Right now, you have 0.5BTC worth of (ins) that funds your transaction! You’ll want to send the rest back to your own wallet. This is called a change address, you can get one using BTCd’s JSON RPC again.

receiving_address = "SOME_ADDRESS_HERE"
change_address = client.request(
"getrawchangeaddress",
"bech32", # bech32 adoption would be very nice, lower tx size.
)
# Lets compute what we'll send back to yourself and what we'll send
#
# total_usable = 2BTC from 1addr
# sending_amount = 0.01BTC + 0.000178 (fees)
change_amount = total_usable - sending_amount

Build the transaction hash (Putting my outputs and inputs together)

Once you’ve determined where you’ll get the money from (inputs), and where it’ll go (outputs). Let’s prepare a json object for createrawtransaction

ins = usable.map do |output|

end
outs = [
,
,
]
outs.inspect
#=> [
,
,
]
tx_to_submit = # raw_tx will be a hex-encoded string
raw_tx = client.request(
"createrawtransaction",
tx_to_submit[:ins],
tx_to_submit[:outs],
0, # locktime
true, # replaceable
)

Sign the transaction

After getting the hex-encoded transaction via createrawtransaction, sign it.

resp = client.request(
"signrawtransactionwithwallet",
raw_tx,
)
if resp["errors"].present?
raise StandardError, "Error with signing transaction - #"
end
signed_tx = resp["hex"]

Send the transaction

You can now send the signed transaction to the local BTCd node and to the network!

# you can check the tx_id returned in a blockchain explorer like 
# blockstream.info or blockchain.com
tx_id = client.request("sendrawtransaction", signed_tx)

Yey! Let’s grab our handy-dandy vcr: Here’s a simple test for all that work we just did.

require "rails_helper"# Put all of the things that we did in some decoupled class so that it's easy to test. Here it assumes we can just write an integration test for a Bitcoin (BTC)zzz::Send class that does everything we talked aboutmodule Bitcoin (BTC)zzz
RSpec.describe Send do
let(:fee)
let(:client) do
Bitcoiner.new(<test_net_credentials>)
end
let(:address)
it(
"sends BTC",
vcr: ,
) do
current_balance = client.request("getbalance")
result_tx_id = described_class.(
tx_fee_in_btc: fee,
destination_address: address,
receivable_amount: 0.002,
)
after_send_balance = client.request("getbalance") # expect the balance differences to be for the tx_fee since we
# sent it back to our self
expected_diff = current_balance - after_send_balance
expect(expected_diff.round(7).to_d).to eq fee
expect(result_tx_id).not_to be_nil
expect(result_tx_id).to be_a String
remote_tx = client.request(
"getrawtransaction",
result_tx_id,
true, # verbose, gets you json instead of a hex string
)

# parse that remote_tx and do your expectations
end

end
end

There you go! You can now send Bitcoin with Ruby. Although, what if this service class gets called a lot? And at the same time?

  • You can probably use something like sidekiq-unique-jobs and line all the withdrawal requests in a worker queue so that you won’t have race conditions.
  • You can also have balance validations at the start via getbalance if the wallet still has spendable money and you can fail the job accordingly.

There are more ways but you can definitely make this a safe service class to use via defensive programming.

Another next-level implementation for this is to not rely on BTCd’s wallet for getting the unspent outputs, building and signing the transaction hashes. This allows you to feed multiple inputs coming from a cold wallet, and multi-sig keys from different signers.

You can use lower-level libraries such as bitcoin-ruby to achieve this, but you’ll have to keep track of your addresses, keys, transactions on your own a.k.a. implementing/complementing BTCd’s wallet capabilities.

Hopefully this guide has eased you into how to work with Bitcoin and BTCd.

Published at Fri, 09 Aug 2019 07:15:33 +0000

Bitcoin Pic Of The Moment
If you enjoy my photos, you are welcome to #‎donate #‎bitcoin to me at: 1Q2LV3bsxZjRBQoRXAXikpUGPCrNeGSUWc
By antwerpenR on 2013-10-12 11:40:42
tags

Previous Article

Sending Bitcoin with Ruby – Ace Subido

Next Article

photo

You might be interested in …