Class: Sibit
- Inherits:
-
Object
- Object
- Sibit
- Defined in:
- lib/sibit.rb,
lib/sibit/btc.rb,
lib/sibit/cex.rb,
lib/sibit/log.rb,
lib/sibit/earn.rb,
lib/sibit/fake.rb,
lib/sibit/http.rb,
lib/sibit/json.rb,
lib/sibit/error.rb,
lib/sibit/bestof.rb,
lib/sibit/firstof.rb,
lib/sibit/version.rb,
lib/sibit/blockchain.rb,
lib/sibit/blockchair.rb,
lib/sibit/cryptoapis.rb,
lib/sibit/bitcoinchain.rb
Overview
Bitcoinchain.com API.
- Author
-
Yegor Bugayenko ([email protected])
- Copyright
-
Copyright © 2019-2023 Yegor Bugayenko
- License
-
MIT
Defined Under Namespace
Classes: BestOf, Bitcoinchain, Blockchain, Blockchair, Btc, Cex, Cryptoapis, Earn, Error, Fake, FirstOf, Http, HttpProxy, Json, Log, NotSupportedError
Constant Summary collapse
- VERSION =
Current version of the library.
'0.24.0'
Instance Method Summary collapse
-
#balance(address) ⇒ Object
Gets the balance of the address, in satoshi.
-
#create(pvt) ⇒ Object
Creates Bitcon address using the private key in Hash160 format.
-
#fees ⇒ Object
Get recommended fees, in satoshi per byte.
-
#generate ⇒ Object
Generates new Bitcon private key and returns in Hash160 format.
-
#height(hash) ⇒ Object
Get the height of the block.
-
#initialize(log: $stdout, api: Sibit::Blockchain.new(log: Sibit::Log.new(log))) ⇒ Sibit
constructor
Constructor.
-
#latest ⇒ Object
Gets the hash of the latest block.
-
#next_of(hash) ⇒ Object
Get the hash of the next block.
-
#pay(amount, fee, sources, target, change, skip_utxo: []) ⇒ Object
Sends a payment and returns the transaction hash.
-
#price(currency = 'USD') ⇒ Object
Current price of 1 BTC in USD (or another currency), float returned.
-
#scan(start, max: 4) ⇒ Object
You call this method and provide a callback.
Constructor Details
#initialize(log: $stdout, api: Sibit::Blockchain.new(log: Sibit::Log.new(log))) ⇒ Sibit
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.
50 51 52 53 |
# File 'lib/sibit.rb', line 50 def initialize(log: $stdout, api: Sibit::Blockchain.new(log: Sibit::Log.new(log))) @log = Sibit::Log.new(log) @api = api end |
Instance Method Details
#balance(address) ⇒ Object
Gets the balance of the address, in satoshi.
76 77 78 79 |
# File 'lib/sibit.rb', line 76 def balance(address) raise Error, "Invalid address #{address.inspect}" unless /^[0-9a-zA-Z]+$/.match?(address) @api.balance(address) end |
#create(pvt) ⇒ Object
Creates Bitcon address using the private key in Hash160 format.
69 70 71 72 73 |
# File 'lib/sibit.rb', line 69 def create(pvt) key = Bitcoin::Key.new key.priv = pvt key.addr end |
#fees ⇒ Object
Get recommended fees, in satoshi per byte. The method returns a hash: { S: 12, M: 45, L: 100, XL: 200 }
95 96 97 |
# File 'lib/sibit.rb', line 95 def fees @api.fees end |
#generate ⇒ Object
Generates new Bitcon private key and returns in Hash160 format.
62 63 64 65 66 |
# File 'lib/sibit.rb', line 62 def generate key = Bitcoin::Key.generate.priv @log.info("Bitcoin private key generated: #{key[0..8]}...") key end |
#height(hash) ⇒ Object
Get the height of the block.
82 83 84 85 |
# File 'lib/sibit.rb', line 82 def height(hash) raise Error, "Invalid block hash #{hash.inspect}" unless /^[0-9a-f]{64}$/.match?(hash) @api.height(hash) end |
#latest ⇒ Object
Gets the hash of the latest block.
177 178 179 |
# File 'lib/sibit.rb', line 177 def latest @api.latest end |
#next_of(hash) ⇒ Object
Get the hash of the next block.
88 89 90 91 |
# File 'lib/sibit.rb', line 88 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, skip_utxo: []) ⇒ Object
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
116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 |
# File 'lib/sibit.rb', line 116 def pay(amount, fee, sources, target, change, skip_utxo: []) 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| if skip_utxo.include?(utxo[:hash]) @log.info("UTXO skipped: #{utxo[:hash]}") next end 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') ⇒ Object
Current price of 1 BTC in USD (or another currency), float returned.
56 57 58 59 |
# File 'lib/sibit.rb', line 56 def price(currency = 'USD') raise Error, "Invalid currency #{currency.inspect}" unless /^[A-Z]{3}$/.match?(currency) @api.price(currency) end |
#scan(start, max: 4) ⇒ Object
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.
191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 |
# File 'lib/sibit.rb', line 191 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.}") 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 |