1# frozen_string_literal: true
2
3require "bigdecimal"
4require "json"
5require "net/http"
6require "securerandom"
7
8class Electrum
9 class NoTransaction < StandardError; end
10
11 def initialize(rpc_uri:, rpc_username:, rpc_password:)
12 @rpc_uri = URI(rpc_uri)
13 @rpc_username = rpc_username
14 @rpc_password = rpc_password
15 end
16
17 def getaddresshistory(address)
18 rpc_call(:getaddresshistory, address: address)["result"]
19 end
20
21 def gettransaction(tx_hash)
22 Transaction.new(self, tx_hash, rpc_call(
23 :deserialize,
24 [rpc_call(:gettransaction, txid: tx_hash)["result"]]
25 )["result"])
26 end
27
28 def get_tx_status(tx_hash)
29 rpc_call(:get_tx_status, txid: tx_hash)["result"]
30 end
31
32 def listaddresses
33 rpc_call(:listaddresses, receiving: true)["result"]
34 end
35
36 def notify(address, url)
37 rpc_call(:notify, address: address, URL: url)["result"]
38 end
39
40 class Transaction
41 def initialize(electrum, tx_hash, tx)
42 raise NoTransaction, "No tx found for #{tx_hash}" unless tx
43
44 @electrum = electrum
45 @tx_hash = tx_hash
46 @tx = tx
47 end
48
49 def confirmations
50 @electrum.get_tx_status(@tx_hash)["confirmations"]
51 end
52
53 def amount_for(*addresses)
54 BigDecimal(
55 @tx["outputs"]
56 .select { |o| addresses.include?(o["address"]) }
57 .map { |o| o["value_sats"] }
58 .sum
59 ) * 0.00000001
60 end
61 end
62
63protected
64
65 def rpc_call(method, params)
66 JSON.parse(post_json(
67 jsonrpc: "2.0",
68 id: SecureRandom.hex,
69 method: method.to_s,
70 params: params
71 ).body)
72 end
73
74 def post_json_req(data)
75 req = Net::HTTP::Post.new(
76 @rpc_uri,
77 "Content-Type" => "application/json"
78 )
79 req.basic_auth(@rpc_username, @rpc_password)
80 req.body = data.to_json
81 req
82 end
83
84 def post_json(data)
85 Net::HTTP.start(
86 @rpc_uri.hostname,
87 @rpc_uri.port,
88 use_ssl: @rpc_uri.scheme == "https"
89 ) do |http|
90 http.request(post_json_req(data))
91 end
92 end
93end