electrum.rb

 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