electrum.rb

 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	def notify(address, url)
38		rpc_call(:notify, address: address, URL: url).then { |r| r["result"] }
39	end
40
41	class Transaction
42		def initialize(electrum, tx_hash, tx)
43			@electrum = electrum
44			@tx_hash = tx_hash
45			@tx = tx
46		end
47
48		def confirmations
49			@electrum.get_tx_status(@tx_hash).then { |r| r["confirmations"] }
50		end
51
52		def amount_for(*addresses)
53			BigDecimal(
54				@tx["outputs"]
55					.select { |o| addresses.include?(o["address"]) }
56					.map { |o| o["value_sats"] }
57					.sum
58			) * 0.00000001
59		end
60	end
61
62protected
63
64	def rpc_call(method, params)
65		post_json(
66			jsonrpc: "2.0",
67			id: SecureRandom.hex,
68			method: method.to_s,
69			params: params
70		).then { |res| JSON.parse(res.response) }
71	end
72
73	def post_json(data)
74		EM::HttpRequest.new(
75			@rpc_uri,
76			tls: { verify_peer: true }
77		).apost(
78			head: {
79				"Authorization" => [@rpc_username, @rpc_password],
80				"Content-Type" => "application/json"
81			},
82			body: data.to_json
83		)
84	end
85end