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