class Sibit

Sibit main class.

Author

Yegor Bugayenko (yegor256@gmail.com)

Copyright

Copyright © 2019-2021 Yegor Bugayenko

License

MIT

API best of.

Author

Yegor Bugayenko (yegor256@gmail.com)

Copyright

Copyright © 2019-2021 Yegor Bugayenko

License

MIT

Bitcoinchain.com API.

Author

Yegor Bugayenko (yegor256@gmail.com)

Copyright

Copyright © 2019-2021 Yegor Bugayenko

License

MIT

Blockchain.info API.

It works through the Blockchain API: www.blockchain.com/api/blockchain_api

Author

Yegor Bugayenko (yegor256@gmail.com)

Copyright

Copyright © 2019-2021 Yegor Bugayenko

License

MIT

Blockchair.com API.

Author

Yegor Bugayenko (yegor256@gmail.com)

Copyright

Copyright © 2019-2021 Yegor Bugayenko

License

MIT

Btc.com API.

Here: btc.com/api-doc

Author

Yegor Bugayenko (yegor256@gmail.com)

Copyright

Copyright © 2019-2021 Yegor Bugayenko

License

MIT

Cex.io API.

Author

Yegor Bugayenko (yegor256@gmail.com)

Copyright

Copyright © 2019-2021 Yegor Bugayenko

License

MIT

Cryptoapis.io API.

Author

Yegor Bugayenko (yegor256@gmail.com)

Copyright

Copyright © 2019-2021 Yegor Bugayenko

License

MIT

Earn.com API.

Author

Yegor Bugayenko (yegor256@gmail.com)

Copyright

Copyright © 2019-2021 Yegor Bugayenko

License

MIT

The error.

Author

Yegor Bugayenko (yegor256@gmail.com)

Copyright

Copyright © 2019-2021 Yegor Bugayenko

License

MIT

Fake API.

Author

Yegor Bugayenko (yegor256@gmail.com)

Copyright

Copyright © 2019-2021 Yegor Bugayenko

License

MIT

API first of.

Author

Yegor Bugayenko (yegor256@gmail.com)

Copyright

Copyright © 2019-2021 Yegor Bugayenko

License

MIT

HTTP interface.

Author

Yegor Bugayenko (yegor256@gmail.com)

Copyright

Copyright © 2019-2021 Yegor Bugayenko

License

MIT

Json SDK.

It works through the Blockchain API: www.blockchain.com/api/blockchain_api

Author

Yegor Bugayenko (yegor256@gmail.com)

Copyright

Copyright © 2019-2021 Yegor Bugayenko

License

MIT

The log.

Author

Yegor Bugayenko (yegor256@gmail.com)

Copyright

Copyright © 2019-2021 Yegor Bugayenko

License

MIT

Sibit main class.

Author

Yegor Bugayenko (yegor256@gmail.com)

Copyright

Copyright © 2019-2021 Yegor Bugayenko

License

MIT

Constants

VERSION

Current version of the library.

Public Class Methods

new(log: $stdout, api: Sibit::Blockchain.new(log: Sibit::Log.new(log))) click to toggle source

Constructor.

You may provide the log you want to see the messages in. If you don't provide anything, the console will be used. The object you provide has to respond to the method info or puts in order to receive logging messages.

It is recommended to wrap the API in a RetriableProxy from retriable_proxy gem and to configure it to retry on Sibit::Error:

RetriableProxy.for_object(api, on: Sibit::Error)

This will help you avoid some temporary network issues.

The api argument can be an object or an array of objects. If an array is provided, we will make an attempt to try them one by one, until one of them succeedes.

# File lib/sibit.rb, line 51
def initialize(log: $stdout, api: Sibit::Blockchain.new(log: Sibit::Log.new(log)))
  @log = Sibit::Log.new(log)
  @api = api
end

Public Instance Methods

balance(address) click to toggle source

Gets the balance of the address, in satoshi.

# File lib/sibit.rb, line 77
def balance(address)
  raise Error, "Invalid address #{address.inspect}" unless /^[0-9a-zA-Z]+$/.match?(address)
  @api.balance(address)
end
create(pvt) click to toggle source

Creates Bitcon address using the private key in Hash160 format.

# File lib/sibit.rb, line 70
def create(pvt)
  key = Bitcoin::Key.new
  key.priv = pvt
  key.addr
end
fees() click to toggle source

Get recommended fees, in satoshi per byte. The method returns a hash: { S: 12, M: 45, L: 100, XL: 200 }

# File lib/sibit.rb, line 96
def fees
  @api.fees
end
generate() click to toggle source

Generates new Bitcon private key and returns in Hash160 format.

# File lib/sibit.rb, line 63
def generate
  key = Bitcoin::Key.generate.priv
  @log.info("Bitcoin private key generated: #{key[0..8]}...")
  key
end
height(hash) click to toggle source

Get the height of the block.

# File lib/sibit.rb, line 83
def height(hash)
  raise Error, "Invalid block hash #{hash.inspect}" unless /^[0-9a-f]{64}$/.match?(hash)
  @api.height(hash)
end
latest() click to toggle source

Gets the hash of the latest block.

# File lib/sibit.rb, line 174
def latest
  @api.latest
end
next_of(hash) click to toggle source

Get the hash of the next block.

# File lib/sibit.rb, line 89
def next_of(hash)
  raise Error, "Invalid block hash #{hash.inspect}" unless /^[0-9a-f]{64}$/.match?(hash)
  @api.next_of(hash)
end
pay(amount, fee, sources, target, change) click to toggle source

Sends a payment and returns the transaction hash.

If the payment can't be signed (the key is wrong, for example) or the previous transaction is not found, or there is a network error, or any other reason, you will get an exception. In this case, just try again. It's safe to try as many times as you need. Don't worry about duplicating your transaction, the Bitcoin network will filter duplicates out.

If there are more than 1000 UTXOs in the address where you are trying to send bitcoins from, this method won't be helpful.

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 hashmap of bitcoin addresses where the coins are now, with their addresses as keys and private keys as values target: the target address to send to change: the address where the change has to be sent to

# File lib/sibit.rb, line 117
  def pay(amount, fee, sources, target, change)
    p = price('USD')
    satoshi = satoshi(amount)
    builder = Bitcoin::Builder::TxBuilder.new
    unspent = 0
    size = 100
    utxos = @api.utxos(sources.keys)
    @log.info("#{utxos.count} UTXOs found, these will be used \
(value/confirmations at tx_hash):")
    utxos.each do |utxo|
      unspent += utxo[:value]
      builder.input do |i|
        i.prev_out(utxo[:hash])
        i.prev_out_index(utxo[:index])
        i.prev_out_script = utxo[:script]
        address = Bitcoin::Script.new(utxo[:script]).get_address
        i.signature_key(key(sources[address]))
      end
      size += 180
      @log.info(
        "  #{num(utxo[:value], p)}/#{utxo[:confirmations]} at #{utxo[:hash]}"
      )
      break if unspent > satoshi
    end
    if unspent < satoshi
      raise Error, "Not enough funds to send #{num(satoshi, p)}, only #{num(unspent, p)} left"
    end
    builder.output(satoshi, target)
    f = mfee(fee, size)
    satoshi += f if f.negative?
    raise Error, "The fee #{f.abs} covers the entire amount" if satoshi.zero?
    raise Error, "The fee #{f.abs} is bigger than the amount #{satoshi}" if satoshi.negative?
    tx = builder.tx(
      input_value: unspent,
      leave_fee: true,
      extra_fee: [f, Bitcoin.network[:min_tx_fee]].max,
      change_address: change
    )
    left = unspent - tx.outputs.map(&:value).inject(&:+)
    @log.info("A new Bitcoin transaction #{tx.hash} prepared:
  #{tx.in.count} input#{tx.in.count > 1 ? 's' : ''}:
    #{tx.inputs.map { |i| " in: #{i.prev_out.bth}:#{i.prev_out_index}" }.join("\n    ")}
  #{tx.out.count} output#{tx.out.count > 1 ? 's' : ''}:
    #{tx.outputs.map { |o| "out: #{o.script.bth} / #{num(o.value, p)}" }.join("\n    ")}
  Min tx fee: #{num(Bitcoin.network[:min_tx_fee], p)}
  Fee requested: #{num(f, p)} as \"#{fee}\"
  Fee actually paid: #{num(left, p)}
  Tx size: #{size} bytes
  Unspent: #{num(unspent, p)}
  Amount: #{num(satoshi, p)}
  Target address: #{target}
  Change address is #{change}")
    @api.push(tx.to_payload.bth)
    tx.hash
  end
price(currency = 'USD') click to toggle source

Current price of 1 BTC in USD (or another currency), float returned.

# File lib/sibit.rb, line 57
def price(currency = 'USD')
  raise Error, "Invalid currency #{currency.inspect}" unless /^[A-Z]{3}$/.match?(currency)
  @api.price(currency)
end
scan(start, max: 4) { |address, hash, satoshi| ... } click to toggle source

You call this method and provide a callback. You provide the hash of the starting block. The method will go through the Blockchain, fetch blocks and find transactions, one by one, passing them to the callback provided. When finished, the method returns the hash of a new block, not scanned yet or NIL if it's the end of Blockchain.

The callback will be called with three arguments: 1) Bitcoin address of the receiver, 2) transaction hash, 3) amount in satoshi. The callback should return non-false if the transaction found was useful.

# File lib/sibit.rb, line 188
  def scan(start, max: 4)
    raise Error, "Invalid block hash #{start.inspect}" unless /^[0-9a-f]{64}$/.match?(start)
    raise Error, "The max number must be above zero: #{max}" if max < 1
    block = start
    count = 0
    wrong = []
    json = {}
    loop do
      json = @api.block(block)
      if json[:orphan]
        steps = 4
        @log.info("Orphan block found at #{block}, moving #{steps} steps back...")
        wrong << block
        steps.times do
          block = json[:previous]
          wrong << block
          @log.info("Moved back to #{block}")
          json = @api.block(block)
        end
        next
      end
      checked = 0
      checked_outputs = 0
      json[:txns].each do |t|
        t[:outputs].each_with_index do |o, i|
          address = o[:address]
          checked_outputs += 1
          hash = "#{t[:hash]}:#{i}"
          satoshi = o[:value]
          if yield(address, hash, satoshi)
            @log.info("Bitcoin tx found at #{hash} for #{satoshi} sent to #{address}")
          end
        end
        checked += 1
      end
      count += 1
      @log.info("We checked #{checked} txns and #{checked_outputs} outputs \
in block #{block} (by #{json[:provider]})")
      block = json[:next]
      begin
        if block.nil?
          @log.info("The next_block is empty in #{json[:hash]}, this may be the end...")
          block = @api.next_of(json[:hash])
        end
      rescue Sibit::Error => e
        @log.info("Failed to get the next_of(#{json[:hash]}), quitting: #{e.message}")
        break
      end
      if block.nil?
        @log.info("The block #{json[:hash]} is definitely the end of Blockchain, we stop.")
        break
      end
      if count > max
        @log.info("Too many blocks (#{count}) in one go, let's get back to it next time")
        break
      end
    end
    @log.info("Scanned from #{start} to #{json[:hash]} (#{count} blocks)")
    json[:hash]
  end

Private Instance Methods

key(hash160) click to toggle source

Make key from private key string in Hash160.

# File lib/sibit.rb, line 288
def key(hash160)
  key = Bitcoin::Key.new
  key.priv = hash160
  key
end
mfee(fee, size) click to toggle source

Calculates a fee in satoshi for the transaction of the specified size. The fee argument could be a number in satoshi, in which case it will be returned as is, or a string like “XL” or “S”, in which case the fee will be calculated using the size argument (which is the size of the transaction in bytes).

# File lib/sibit.rb, line 274
def mfee(fee, size)
  return fee.to_i if fee.is_a?(Integer)
  raise Error, 'Fee should either be a String or Integer' unless fee.is_a?(String)
  mul = 1
  if fee.start_with?('+', '-')
    mul = -1 if fee.start_with?('-')
    fee = fee[1..-1]
  end
  sat = fees[fee.to_sym]
  raise Error, "Can't understand the fee: #{fee.inspect}" if sat.nil?
  mul * sat * size
end
num(satoshi, usd) click to toggle source
# File lib/sibit.rb, line 251
def num(satoshi, usd)
  format(
    '%<satoshi>ss/$%<dollars>0.2f',
    satoshi: satoshi.to_s.gsub(/\d(?=(...)+$)/, '\0,'),
    dollars: satoshi * usd / 100_000_000
  )
end
satoshi(amount) click to toggle source

Convert text to amount.

# File lib/sibit.rb, line 260
def satoshi(amount)
  return amount if amount.is_a?(Integer)
  unless amount.is_a?(String)
    raise Error, "Amount should either be a String or Integer, #{amount.class.name} provided"
  end
  return (amount.gsub(/BTC$/, '').to_f * 100_000_000).to_i if amount.end_with?('BTC')
  raise Error, "Can't understand the amount #{amount.inspect}"
end