How to send bitcoins using ruby

How to send bitcoins using ruby

Introduction

In this blogpost I explain how to propagate a bitcoin transaction to the
bitcoin net using the helloblock.io API. First, I explain how to create
a transaction using the bitcoin-ruby gem. Second, I show you how to
propagate the transaction to the bitcoin network.

Create the bitcoin testnet3 address

We are using the Bitcoin-ruby gem. Below, we are first setting the
network to testnet3, so we do not spend real bitcoins. We then
create a bitcoin address.

require 'bitcoin'
Bitcoin.network = :testnet3
private_key, public_key = Bitcoin::generate_key
address = Bitcoin::pubkey_to_address(public_key)

Let’s assume we generated the following addresses:

address = 'mmp6qqAJN5Wkd58vBqtgMLgK3zeozx1GSX'

priv_key = '11deaf5ce854908762a80f1dc5fcee8c4126a9288b999725e730eb5ccd6ef9e0'
publ_key = "0400ce81e03a167efc8277c3f39b9aef2c36d22cef0c82c996e862caa6b3a404f4034dddaf37c2686aabfb5e12780b06ef93a53a290029ed99c986f7d9c526acf2"

We now need a few test-bitcoins, so send a few testnet bitcoins to your address, for example
via Testnet3 Fauet

Create the raw transaction

In order to generate a new transaction, we need to know from which
previous transaction we are using the funds from and the address, where
the bitcoins should be transfered to.

Below you see the bitcoin-ruby builder block. In our example we show
a simple transaction consisting of one input and one output.

The input consists of the previous transactions from where the bitcoins are
taken. The input also needs the key to unlock the funds from the previous
transaction. Without the key you cannot transfer the bitcoins.

The ouput simply consists of the amount and the script used to unlock
the funds. Normally, the script is simply a check that the key corresponds
to the address. But more complicated scripts are possible, such as
multi-signature and others. An important issue is that all funds from the input
will be used, that is, the remaining bitcoins from the input not sent to an
address in the output will simply be lost (to be precise, they are given to
the miner). So make sure that all bitcoins from the input are also given an
output, and if only back to your address.

require 'bitcoin'
include Bitcoin::Builder

new_tx = build_tx do |t|
  t.input do |i|
    # i.prev_out prev_tx
    i.prev_out prev_transaction
    i.prev_out_index prev_out_index
    i.signature_key key
  end
  t.output do |o|
    o.value 20000000 # 0.2 BTC, denominated in satoshi
    o.script {|s| s.type :address; s.recipient addr }
  end
end

How to find the unspent transactions

To send the transaction to the network we use helloblock.io and its ruby gem.
First, I simply show you how to use the api, before going into how to send transactions.

require 'helloblock'
HelloBlock.network = :testnet # or :mainnet
HelloBlock::Address.find('mmp6qqAJN5Wkd58vBqtgMLgK3zeozx1GSX')
# to get the balance
HelloBlock::Address.find('mmp6qqAJN5Wkd58vBqtgMLgK3zeozx1GSX')["data"]["address"]["balance"]
# or
HelloBlock::Address.find('mmp6qqAJN5Wkd58vBqtgMLgK3zeozx1GSX')["data"]["address"]["confirmedBalance"]

In order to create a transactions, you need to find unspent outputs directing to your address.
With helloblock you find these with:

HelloBlock::Address.find('mmp6qqAJN5Wkd58vBqtgMLgK3zeozx1GSX').unspents

The important data to extract from the unspents are the transaction ids:

transaction_hashes = HelloBlock::Address.find('mmp6qqAJN5Wkd58vBqtgMLgK3zeozx1GSX').unspents["data"]["unspents"].map{|x| x["txHash"]}

Create the transaction, part II

Now that we have the input transactions we can spend, we can create the real transaction. Let’s assume
we have enough bitcoins available from the first unspent transaction.

require 'bitcoin'
require 'helloblock'
require 'open-uri'

Bitcoin.network = :testnet3

# Let's first define a methdo to build up the transaction
def build_transaction(prev_tx, prev_out_index, key, value, addr)
  include Bitcoin::Builder
  new_tx = build_tx do |t|
    t.input do |i|
      # i.prev_out prev_tx
      i.prev_out prev_tx
      i.prev_out_index prev_out_index
      i.signature_key key
    end
    t.output do |o|
      o.value value # 0.49 BTC, leave 0.01 BTC as fee
      o.script {|s| s.type :address; s.recipient addr }
    end
    t.output do |o|
      o.to "ebbe", :op_return
      o.value 0
    end
  end
end

# Function to convert the binary representation to hex
def bin_to_hex(s)
  s.unpack('H*').first
end

priv_key = '11deaf5ce854908762a80f1dc5fcee8c4126a9288b999725e730eb5ccd6ef9e0'
publ_key = "0400ce81e03a167efc8277c3f39b9aef2c36d22cef0c82c996e862caa6b3a404f4034dddaf37c2686aabfb5e12780b06ef93a53a290029ed99c986f7d9c526acf2"
address = 'mmp6qqAJN5Wkd58vBqtgMLgK3zeozx1GSX'
key = Bitcoin::Key.new(priv_key, publ_key)
unspents = HelloBlock::Address.find(address).unspents["data"]["unspents"]

# And we take the first input, assuming it has enough bitcoins transfered to us
tx_hash = unspents.first["txHash"]
tx_value = unspents.first["value"]
tx_index = unspents.first["index"]

query = open("http://test.webbtc.com/tx/#{tx_hash}.json")
prev_tx = Bitcoin::P::Tx.from_json(query)

# We simply make a transaction, where all funds are transfered back to our address
tx = build_transaction(prev_tx, tx_index, key, tx_value, address)

puts tx.to_json
tx_bin = tx.to_payload

# tx_hex = bin_to_hex(tx.to_payload)
# res = HelloBlock::Transaction.propagate(tx_hex)
# res = Bitcoin::Protocol::Tx.new(tx_hex)
# puts res
# curl -d 'tx_hex=' https://chain.so/api/v2/send_tx/DOGE

require 'rest_client'
response = RestClient.post "https://chain.so/api/v2/send_tx/BTCTEST", :tx_hex => tx_hex
if response.code == 200 then
  content = JSON.parse response.to_str
  puts content
else
  puts "Error"
  puts response: response
end

response = RestClient.get "https://chain.so/api/v2/get_tx/BTCTEST/#{tx.hash}"
if response.code == 200 then
  content = JSON.parse response.to_str
  puts content
else
  puts "Error"
  puts response: response
end


# require 'net/http'
# params = {'tx_hex' => tx_hex}
# url = URI.parse('https://chain.so/api/v2/send_tx/BTCTEST')
# resp, data = Net::HTTP.post_form(url, params)
# puts resp.inspect
# puts data.inspect

In case you get a `NoMethodError: undefined method `bytesize’ for nil:NilClass` error,
most likely you forgot to add `Bitcoin.network = :testnet3`. It took me quite some
time to figure out this error.

How to send the transaction to the bitcoin/testnet3 network

Bonus: How to get the current rate

uri = URI("http://openexchangerates.org/api/latest.json?app_id=#{BitcoinPayable.config.open_exchange_key}")
response = JSON.parse(Net::HTTP.get(uri))
response["rates"]