lib/sibit.rb in sibit-0.0.1 vs lib/sibit.rb in sibit-0.1.0
- old
+ new
@@ -23,40 +23,144 @@
require 'bitcoin'
require 'typhoeus'
require 'json'
# Sibit main class.
+#
+# It works through the Blockchain API at the moment:
+# https://www.blockchain.com/api/blockchain_api
+#
# Author:: Yegor Bugayenko (yegor256@gmail.com)
# Copyright:: Copyright (c) 2019 Yegor Bugayenko
# License:: MIT
class Sibit
- # Generate new Bitcon private key.
+ # Constructor.
+ def initialize(log: STDOUT)
+ @log = log
+ end
+
+ # Generate new Bitcon private key and returns in Hash160 format.
def generate
- key = Bitcoin::Key.generate
- key.priv
+ Bitcoin::Key.generate.priv
end
- # Create Bitcon address using the private key.
+ # Create Bitcon address using the private key in Hash160 format.
def create(pvt)
- key = Bitcoin::Key.new
- key.priv = pvt
- key.addr
+ key(pvt).addr
end
# Get the balance of the address, in satoshi.
def balance(address)
- request = Typhoeus::Request.new(
- "https://blockchain.info/rawaddr/#{address}",
- method: :get,
- headers: {}
+ get_json("https://blockchain.info/rawaddr/#{address}")['final_balance']
+ end
+
+ # Send a payment and return the transaction hash.
+ #
+ # +pvt+: the private key as a Hash160 string
+ # +amount+: the amount either in satoshis or ending with 'BTC', like '0.7BTC'
+ # +fee+: the miners fee in satoshis (as integer) or S/M/X/XL as a string
+ # +sources+: the array of bitcoin addresses where the coins are now
+ # +target+: the target address to send to
+ # +change+: the address where the change has to be sent to
+ def pay(pvt, amount, fee, sources, target, change)
+ satoshi = satoshi(amount)
+ builder = Bitcoin::Builder::TxBuilder.new
+ unspent = 0
+ size = 100
+ utxos(sources).each do |utxo|
+ unspent += utxo['value']
+ builder.input do |i|
+ i.prev_out(utxo['tx_hash_big_endian'])
+ i.prev_out_index(utxo['tx_output_n'])
+ i.prev_out_script = [utxo['script']].pack('H*')
+ i.signature_key(key(pvt))
+ end
+ size += 180
+ break if unspent > satoshi
+ end
+ raise "Not enough funds to send #{amount}, only #{unspent} left" if unspent < satoshi
+ builder.output(satoshi, target)
+ tx = builder.tx(
+ input_value: unspent,
+ leave_fee: mfee(fee, size),
+ change_address: change
)
+ post_tx(tx.to_payload.bth)
+ tx.hash
+ end
+
+ private
+
+ # Retrieve all unspent outputs of the given list of
+ # addresses.
+ def utxos(sources)
+ offset = 0
+ txns = []
+ loop do
+ uri = [
+ 'https://blockchain.info/unspent?',
+ "active=#{sources.join('|')}",
+ offset.positive? ? "&offset=#{offset}" : ''
+ ].join
+ list = get_json(uri)['unspent_outputs']
+ txns += list
+ break if list.empty?
+ offset += list.count
+ end
+ txns
+ end
+
+ # Convert text to amount.
+ def satoshi(amount)
+ return (amount.gsub(/BTC$/, '').to_f * 100_000_000).to_i if amount.end_with?('BTC')
+ amount.to_i
+ end
+
+ def mfee(fee, size)
+ return fee.to_i if fee.is_a?(Integer) || /^[0-9]+$/.match?(fee)
+ case fee
+ when 'S'
+ return 10 * size
+ when 'M'
+ return 50 * size
+ when 'L'
+ return 100 * size
+ when 'XL'
+ return 250 * size
+ else
+ raise "Can't understand the fee: #{fee.inspect}"
+ end
+ end
+
+ # Make key from private key string in Hash160.
+ def key(hash160)
+ key = Bitcoin::Key.new
+ key.priv = hash160
+ key
+ end
+
+ def post_tx(body)
+ uri = 'https://blockchain.info/pushtx'
+ request = Typhoeus::Request.new(uri, method: :post, body: { tx: body })
request.run
response = request.response
- json = JSON.parse(response.body)
- json['final_balance']
+ raise "Invalid response at #{uri}: #{response.code}" unless response.code == 200
+ debug("POST #{uri}: #{response.code}")
end
- # Send a payment.
- def pay(_pvt, _amount, _fee, _sources, _target)
- raise 'Not implemented yet'
+ def get_json(uri)
+ request = Typhoeus::Request.new(uri, method: :get, headers: {})
+ request.run
+ response = request.response
+ raise "Invalid response at #{uri}: #{response.code}" unless response.code == 200
+ debug("GET #{uri}: #{response.code}")
+ JSON.parse(response.body)
+ end
+
+ def debug(msg)
+ if @log.respond_to?(:debug)
+ @log.debug(msg)
+ elsif @log.respond_to?(:puts)
+ @log.puts(msg)
+ end
end
end