1# frozen_string_literal: true
2
3require "bigdecimal"
4require "em-http"
5require "em_promise"
6require "em-synchrony/em-http" # For apost vs post
7require "json"
8require "securerandom"
9
10class Electrum
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 createnewaddress
18 rpc_call(:createnewaddress, {}).then { |r| r["result"] }
19 end
20
21 def getaddresshistory(address)
22 rpc_call(:getaddresshistory, address: address).then { |r| r["result"] }
23 end
24
25 def gettransaction(tx_hash)
26 rpc_call(:gettransaction, txid: tx_hash).then { |tx|
27 rpc_call(:deserialize, [tx["result"]])
28 }.then do |tx|
29 Transaction.new(self, tx_hash, tx["result"])
30 end
31 end
32
33 def get_tx_status(tx_hash)
34 rpc_call(:get_tx_status, txid: tx_hash).then { |r| r["result"] }
35 end
36
37 class Transaction
38 def initialize(electrum, tx_hash, tx)
39 @electrum = electrum
40 @tx_hash = tx_hash
41 @tx = tx
42 end
43
44 def confirmations
45 @electrum.get_tx_status(@tx_hash).then { |r| r["confirmations"] }
46 end
47
48 def amount_for(*addresses)
49 BigDecimal.new(
50 @tx["outputs"]
51 .select { |o| addresses.include?(o["address"]) }
52 .map { |o| o["value_sats"] }
53 .sum
54 ) * 0.00000001
55 end
56 end
57
58protected
59
60 def rpc_call(method, params)
61 post_json(
62 jsonrpc: "2.0",
63 id: SecureRandom.hex,
64 method: method.to_s,
65 params: params
66 ).then { |res| JSON.parse(res.response) }
67 end
68
69 def post_json(data)
70 EM::HttpRequest.new(
71 @rpc_uri,
72 tls: { verify_peer: true }
73 ).apost(
74 head: {
75 "Authorization" => [@rpc_username, @rpc_password],
76 "Content-Type" => "application/json"
77 },
78 body: data.to_json
79 )
80 end
81end