Class: Sibit
- Inherits:
-
Object
- Object
- Sibit
- Defined in:
- lib/sibit.rb,
lib/sibit/version.rb
Overview
Sibit main class.
- Author
-
Yegor Bugayenko ([email protected])
- Copyright
-
Copyright © 2019 Yegor Bugayenko
- License
-
MIT
Defined Under Namespace
Constant Summary collapse
- VERSION =
Current version of the library.
'0.12.5'
Class Method Summary collapse
-
.default_http ⇒ Object
This HTTP client will be used by default.
-
.proxy_http(addr) ⇒ Object
This HTTP client with proxy.
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.
-
#get_json(uri) ⇒ Object
Send GET request to the Blockchain API and return JSON response.
-
#initialize(log: STDOUT, http: Sibit.default_http, dry: false, attempts: 1) ⇒ Sibit
constructor
Constructor.
-
#latest ⇒ Object
Gets the hash of the latest block.
-
#pay(amount, fee, sources, target, change) ⇒ Object
Sends a payment and returns the transaction hash.
-
#price(cur = 'USD') ⇒ Object
Current price of 1 BTC.
Constructor Details
#initialize(log: STDOUT, http: Sibit.default_http, dry: false, attempts: 1) ⇒ 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.
98 99 100 101 102 103 |
# File 'lib/sibit.rb', line 98 def initialize(log: STDOUT, http: Sibit.default_http, dry: false, attempts: 1) @log = log @http = http @dry = dry @attempts = attempts end |
Class Method Details
.default_http ⇒ Object
This HTTP client will be used by default.
78 79 80 81 82 |
# File 'lib/sibit.rb', line 78 def self.default_http http = Net::HTTP.new('blockchain.info', 443) http.use_ssl = true http end |
.proxy_http(addr) ⇒ Object
This HTTP client with proxy.
85 86 87 88 89 90 |
# File 'lib/sibit.rb', line 85 def self.proxy_http(addr) host, port = addr.split(':') http = Net::HTTP.new('blockchain.info', 443, host, port.to_i) http.use_ssl = true http end |
Instance Method Details
#balance(address) ⇒ Object
Gets the balance of the address, in satoshi.
125 126 127 128 129 130 |
# File 'lib/sibit.rb', line 125 def balance(address) json = get_json("/rawaddr/#{address}") info("Total transactions: #{json['n_tx']}") info("Received/sent: #{json['total_received']}/#{json['total_sent']}") json['final_balance'] end |
#create(pvt) ⇒ Object
Creates Bitcon address using the private key in Hash160 format.
120 121 122 |
# File 'lib/sibit.rb', line 120 def create(pvt) key(pvt).addr end |
#fees ⇒ Object
Get recommended fees, in satoshi per byte. The method returns a hash: { S: 12, M: 45, L: 100, XL: 200 }
134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 |
# File 'lib/sibit.rb', line 134 def fees json = JSON.parse( Net::HTTP.get( URI('https://bitcoinfees.earn.com/api/v1/fees/recommended') ) ) info("Current recommended Bitcoin fees: \ #{json['hourFee']}/#{json['halfHourFee']}/#{json['fastestFee']} sat/byte") { S: json['hourFee'] / 3, M: json['hourFee'], L: json['halfHourFee'], XL: json['fastestFee'] } end |
#generate ⇒ Object
Generates new Bitcon private key and returns in Hash160 format.
113 114 115 116 117 |
# File 'lib/sibit.rb', line 113 def generate key = Bitcoin::Key.generate.priv info("Bitcoin private key generated: #{key[0..8]}...") key end |
#get_json(uri) ⇒ Object
Send GET request to the Blockchain API and return JSON response. This method will also log the process and will validate the response for correctness.
232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 |
# File 'lib/sibit.rb', line 232 def get_json(uri) start = Time.now attempt = 0 begin res = @http.get( uri, 'Accept' => 'text/plain', 'User-Agent' => user_agent, 'Accept-Encoding' => '' ) raise Error, "Failed to retrieve #{uri} (#{res.code}): #{res.body}" unless res.code == '200' info("GET #{uri}: #{res.code}/#{res.body.length}b in #{age(start)}") JSON.parse(res.body) rescue StandardError => e attempt += 1 raise e if attempt >= @attempts retry end end |
#latest ⇒ Object
Gets the hash of the latest block.
225 226 227 |
# File 'lib/sibit.rb', line 225 def latest get_json('/latestblock')['hash'] end |
#pay(amount, fee, sources, target, change) ⇒ 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
167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 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 |
# File 'lib/sibit.rb', line 167 def pay(amount, fee, sources, target, change) p = price satoshi = satoshi(amount) f = mfee(fee, size_of(amount, sources)) 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? builder = Bitcoin::Builder::TxBuilder.new unspent = 0 size = 100 utxos = get_json( "/unspent?active=#{sources.keys.join('|')}&limit=1000" )['unspent_outputs'] 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['tx_hash_big_endian']) i.prev_out_index(utxo['tx_output_n']) i.prev_out_script = [utxo['script']].pack('H*') address = Bitcoin::Script.new([utxo['script']].pack('H*')).get_address i.signature_key(key(sources[address])) end size += 180 info(" #{num(utxo['value'], p)}/#{utxo['confirmations']} at #{utxo['tx_hash_big_endian']}") 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) 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(&:+) 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 left: #{num(left, p)} Tx size: #{size} bytes Unspent: #{num(unspent, p)} Amount: #{num(satoshi, p)} Target address: #{target} Change address is #{change}") post_tx(tx.to_payload.bth) unless @dry tx.hash end |
#price(cur = 'USD') ⇒ Object
Current price of 1 BTC.
106 107 108 109 110 |
# File 'lib/sibit.rb', line 106 def price(cur = 'USD') h = get_json('/ticker')[cur.upcase] raise Error, "Unrecognized currency #{cur}" if h.nil? h['15m'] end |