Class: Sibit::Sochain

Inherits:
Object
  • Object
show all
Defined in:
lib/sibit/sochain.rb

Overview

SoChain API (formerly known as Block.io).

Documentation: https://sochain.com/api

Author

Yegor Bugayenko (yegor256@gmail.com)

Copyright

Copyright (c) 2019-2026 Yegor Bugayenko

License

MIT

Instance Method Summary collapse

Constructor Details

#initialize(log: Loog::NULL, http: Sibit::Http.new, network: 'BTC') ⇒ Sochain

Constructor.



24
25
26
27
28
# File 'lib/sibit/sochain.rb', line 24

def initialize(log: Loog::NULL, http: Sibit::Http.new, network: 'BTC')
  @http = http
  @log = log
  @network = network
end

Instance Method Details

#balance(address) ⇒ Object

Gets the balance of the address, in satoshi.



71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
# File 'lib/sibit/sochain.rb', line 71

def balance(address)
  data = Sibit::Json.new(http: @http, log: @log).get(
    Iri.new('https://sochain.com/api/v2/get_address_balance').append(@network).append(address)
  )['data']
  if data.nil?
    @log.debug("The balance of #{address} is probably zero (not found)")
    return 0
  end
  confirmed = data['confirmed_balance']
  if confirmed.nil?
    @log.debug("The balance of #{address} is probably zero (no confirmed_balance)")
    return 0
  end
  b = Integer((Float(confirmed) * 100_000_000).round)
  @log.debug("The balance of #{address} is #{b} satoshi")
  b
end

#block(hash) ⇒ Object

This method should fetch a block and return it as a hash.

Raises:



151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
# File 'lib/sibit/sochain.rb', line 151

def block(hash)
  data = Sibit::Json.new(http: @http, log: @log).get(
    Iri.new('https://sochain.com/api/v2/block').append(@network).append(hash)
  )['data']
  raise(Sibit::Error, "The block #{hash} not found") if data.nil?
  nxt = data['next_blockhash']
  nxt = nil if nxt == '0000000000000000000000000000000000000000000000000000000000000000'
  {
    provider: self.class.name,
    hash: data['blockhash'],
    orphan: data['is_orphan'] == true,
    next: nxt,
    previous: data['previous_blockhash'],
    txns: (data['txs'] || []).map do |t|
      unless t.is_a?(Hash)
        raise(Sibit::NotSupportedError, "SoChain returned bare txid #{t} without outputs")
      end
      {
        hash: t['txid'],
        outputs: (t['outputs'] || []).map do |o|
          {
            address: o['address'],
            value: Integer((Float(o['value']) * 100_000_000).round)
          }
        end
      }
    end
  }
end

#feesObject

Get recommended fees, in satoshi per byte.



90
91
92
# File 'lib/sibit/sochain.rb', line 90

def fees
  raise(Sibit::NotSupportedError, 'SoChain doesn\'t provide recommended fees')
end

#height(hash) ⇒ Object

The height of the block, identified by hash.

Raises:



46
47
48
49
50
51
52
53
54
55
# File 'lib/sibit/sochain.rb', line 46

def height(hash)
  data = Sibit::Json.new(http: @http, log: @log).get(
    Iri.new('https://sochain.com/api/v2/block').append(@network).append(hash)
  )['data']
  raise(Sibit::Error, "The block #{hash} not found") if data.nil?
  h = data['block_no']
  raise(Sibit::Error, "The block #{hash} found but the height is absent") if h.nil?
  @log.debug("The height of #{hash} is #{h}")
  Integer(h)
end

#latestObject

Gets the hash of the latest block.

Raises:



95
96
97
98
99
100
101
102
103
104
# File 'lib/sibit/sochain.rb', line 95

def latest
  data = Sibit::Json.new(http: @http, log: @log).get(
    Iri.new('https://sochain.com/api/v2/get_info').append(@network)
  )['data']
  raise(Sibit::Error, 'The latest block info not found') if data.nil?
  hash = data['blockhash']
  raise(Sibit::Error, 'The latest block hash is absent') if hash.nil?
  @log.debug("The latest block hash is #{hash}")
  hash
end

#next_of(hash) ⇒ Object

Get hash of the block after this one.

Raises:



58
59
60
61
62
63
64
65
66
67
68
# File 'lib/sibit/sochain.rb', line 58

def next_of(hash)
  data = Sibit::Json.new(http: @http, log: @log).get(
    Iri.new('https://sochain.com/api/v2/block').append(@network).append(hash)
  )['data']
  raise(Sibit::Error, "The block #{hash} not found") if data.nil?
  nxt = data['next_blockhash']
  nxt = nil if nxt == '0000000000000000000000000000000000000000000000000000000000000000'
  @log.debug("In SoChain the block #{hash} is the latest, there is no next block") if nxt.nil?
  @log.debug("The next block of #{hash} is #{nxt}") unless nxt.nil?
  nxt
end

#price(currency = 'USD') ⇒ Object

Current price of BTC in USD (float returned).

Raises:



31
32
33
34
35
36
37
38
39
40
41
42
43
# File 'lib/sibit/sochain.rb', line 31

def price(currency = 'USD')
  data = Sibit::Json.new(http: @http, log: @log).get(
    Iri.new('https://sochain.com/api/v2/get_price').append(@network).append(currency)
  )['data']
  raise(Sibit::Error, "No price data returned for #{@network}/#{currency}") if data.nil?
  prices = data['prices']
  if prices.nil? || prices.empty?
    raise(Sibit::Error, "No price quotes for #{@network}/#{currency}")
  end
  price = Float(prices[0]['price'])
  @log.debug("The price of #{@network} is #{price} #{currency}")
  price
end

#push(hex) ⇒ Object

Push this transaction (in hex format) to the network. SoChain expects a JSON payload with the tx_hex field on POST /send_tx/network.



132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
# File 'lib/sibit/sochain.rb', line 132

def push(hex)
  uri = URI(Iri.new('https://sochain.com/api/v2/send_tx').append(@network).to_s)
  res = @http.client(uri).post(
    uri.path,
    JSON.generate(tx_hex: hex),
    {
      'Accept' => 'application/json',
      'Content-Type' => 'application/json',
      'Accept-Charset' => 'UTF-8',
      'Accept-Encoding' => ''
    }
  )
  unless res.code == '200'
    raise(Sibit::Error, "Failed to push transaction to #{uri}: #{res.code}\n#{res.body}")
  end
  @log.debug("Transaction (#{hex.length} chars in hex) has been pushed to SoChain")
end

#utxos(sources) ⇒ Object

Fetch all unspent outputs per address. The argument is an array of Bitcoin addresses.



108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
# File 'lib/sibit/sochain.rb', line 108

def utxos(sources)
  out = []
  sources.each do |address|
    data = Sibit::Json.new(http: @http, log: @log).get(
      Iri.new('https://sochain.com/api/v2/get_tx_unspent').append(@network).append(address)
    )['data']
    raise(Sibit::Error, "The address #{address} unspent lookup returned no data") if data.nil?
    txs = data['txs']
    raise(Sibit::Error, "The address #{address} unspent lookup returned no txs") if txs.nil?
    txs.each do |u|
      out << {
        value: Integer((Float(u['value']) * 100_000_000).round),
        hash: u['txid'],
        index: u['output_no'],
        confirmations: u['confirmations'],
        script: [u['script_hex']].pack('H*')
      }
    end
  end
  out
end