bitcoin-ruby 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +12 -0
- data/COPYING +18 -0
- data/Gemfile +4 -0
- data/README.rdoc +189 -0
- data/Rakefile +104 -0
- data/bin/bitcoin_dns_seed +130 -0
- data/bin/bitcoin_gui +80 -0
- data/bin/bitcoin_node +174 -0
- data/bin/bitcoin_shell +12 -0
- data/bin/bitcoin_wallet +323 -0
- data/bitcoin-ruby.gemspec +27 -0
- data/concept-examples/blockchain-pow.rb +151 -0
- data/doc/CONFIG.rdoc +66 -0
- data/doc/EXAMPLES.rdoc +9 -0
- data/doc/NODE.rdoc +35 -0
- data/doc/STORAGE.rdoc +21 -0
- data/doc/WALLET.rdoc +102 -0
- data/examples/balance.rb +60 -0
- data/examples/bbe_verify_tx.rb +55 -0
- data/examples/connect.rb +36 -0
- data/examples/relay_tx.rb +22 -0
- data/examples/verify_tx.rb +57 -0
- data/lib/bitcoin.rb +370 -0
- data/lib/bitcoin/builder.rb +266 -0
- data/lib/bitcoin/config.rb +56 -0
- data/lib/bitcoin/connection.rb +126 -0
- data/lib/bitcoin/ffi/openssl.rb +121 -0
- data/lib/bitcoin/gui/addr_view.rb +42 -0
- data/lib/bitcoin/gui/bitcoin-ruby.png +0 -0
- data/lib/bitcoin/gui/bitcoin-ruby.svg +80 -0
- data/lib/bitcoin/gui/conn_view.rb +36 -0
- data/lib/bitcoin/gui/connection.rb +68 -0
- data/lib/bitcoin/gui/em_gtk.rb +28 -0
- data/lib/bitcoin/gui/gui.builder +1643 -0
- data/lib/bitcoin/gui/gui.rb +290 -0
- data/lib/bitcoin/gui/helpers.rb +113 -0
- data/lib/bitcoin/gui/tree_view.rb +82 -0
- data/lib/bitcoin/gui/tx_view.rb +67 -0
- data/lib/bitcoin/key.rb +125 -0
- data/lib/bitcoin/logger.rb +65 -0
- data/lib/bitcoin/network/command_client.rb +93 -0
- data/lib/bitcoin/network/command_handler.rb +179 -0
- data/lib/bitcoin/network/connection_handler.rb +274 -0
- data/lib/bitcoin/network/node.rb +399 -0
- data/lib/bitcoin/protocol.rb +140 -0
- data/lib/bitcoin/protocol/address.rb +48 -0
- data/lib/bitcoin/protocol/alert.rb +47 -0
- data/lib/bitcoin/protocol/block.rb +154 -0
- data/lib/bitcoin/protocol/handler.rb +38 -0
- data/lib/bitcoin/protocol/parser.rb +148 -0
- data/lib/bitcoin/protocol/tx.rb +205 -0
- data/lib/bitcoin/protocol/txin.rb +97 -0
- data/lib/bitcoin/protocol/txout.rb +73 -0
- data/lib/bitcoin/protocol/version.rb +70 -0
- data/lib/bitcoin/script.rb +634 -0
- data/lib/bitcoin/storage/dummy.rb +164 -0
- data/lib/bitcoin/storage/models.rb +133 -0
- data/lib/bitcoin/storage/sequel.rb +335 -0
- data/lib/bitcoin/storage/sequel_store/sequel_migrations.rb +84 -0
- data/lib/bitcoin/storage/storage.rb +243 -0
- data/lib/bitcoin/version.rb +3 -0
- data/lib/bitcoin/wallet/coinselector.rb +30 -0
- data/lib/bitcoin/wallet/keygenerator.rb +75 -0
- data/lib/bitcoin/wallet/keystore.rb +203 -0
- data/lib/bitcoin/wallet/txdp.rb +116 -0
- data/lib/bitcoin/wallet/wallet.rb +243 -0
- data/spec/bitcoin/bitcoin_spec.rb +472 -0
- data/spec/bitcoin/builder_spec.rb +90 -0
- data/spec/bitcoin/fixtures/0d0affb5964abe804ffe85e53f1dbb9f29e406aa3046e2db04fba240e63c7fdd.json +27 -0
- data/spec/bitcoin/fixtures/23b397edccd3740a74adb603c9756370fafcde9bcc4483eb271ecad09a94dd63.json +23 -0
- data/spec/bitcoin/fixtures/477fff140b363ec2cc51f3a65c0c58eda38f4d41f04a295bbd62babf25e4c590.json +27 -0
- data/spec/bitcoin/fixtures/60a20bd93aa49ab4b28d514ec10b06e1829ce6818ec06cd3aabd013ebcdc4bb1.json +45 -0
- data/spec/bitcoin/fixtures/bc179baab547b7d7c1d5d8d6f8b0cc6318eaa4b0dd0a093ad6ac7f5a1cb6b3ba.json +34 -0
- data/spec/bitcoin/fixtures/rawblock-0.bin +0 -0
- data/spec/bitcoin/fixtures/rawblock-0.json +39 -0
- data/spec/bitcoin/fixtures/rawblock-1.bin +0 -0
- data/spec/bitcoin/fixtures/rawblock-1.json +39 -0
- data/spec/bitcoin/fixtures/rawblock-131025.bin +0 -0
- data/spec/bitcoin/fixtures/rawblock-131025.json +5063 -0
- data/spec/bitcoin/fixtures/rawblock-170.bin +0 -0
- data/spec/bitcoin/fixtures/rawblock-170.json +68 -0
- data/spec/bitcoin/fixtures/rawblock-9.bin +0 -0
- data/spec/bitcoin/fixtures/rawblock-9.json +39 -0
- data/spec/bitcoin/fixtures/rawblock-testnet-26478.bin +0 -0
- data/spec/bitcoin/fixtures/rawblock-testnet-26478.json +64 -0
- data/spec/bitcoin/fixtures/rawtx-01.bin +0 -0
- data/spec/bitcoin/fixtures/rawtx-01.json +27 -0
- data/spec/bitcoin/fixtures/rawtx-02.bin +0 -0
- data/spec/bitcoin/fixtures/rawtx-02.json +27 -0
- data/spec/bitcoin/fixtures/rawtx-03.bin +0 -0
- data/spec/bitcoin/fixtures/rawtx-03.json +48 -0
- data/spec/bitcoin/fixtures/rawtx-04.json +27 -0
- data/spec/bitcoin/fixtures/rawtx-0437cd7f8525ceed2324359c2d0ba26006d92d856a9c20fa0241106ee5a597c9.bin +0 -0
- data/spec/bitcoin/fixtures/rawtx-05.json +23 -0
- data/spec/bitcoin/fixtures/rawtx-14be6fff8c6014f7c9493b4a6e4a741699173f39d74431b6b844fcb41ebb9984.bin +0 -0
- data/spec/bitcoin/fixtures/rawtx-2f4a2717ec8c9f077a87dde6cbe0274d5238793a3f3f492b63c744837285e58a.bin +0 -0
- data/spec/bitcoin/fixtures/rawtx-2f4a2717ec8c9f077a87dde6cbe0274d5238793a3f3f492b63c744837285e58a.json +27 -0
- data/spec/bitcoin/fixtures/rawtx-406b2b06bcd34d3c8733e6b79f7a394c8a431fbf4ff5ac705c93f4076bb77602.json +23 -0
- data/spec/bitcoin/fixtures/rawtx-52250a162c7d03d2e1fbc5ebd1801a88612463314b55102171c5b5d817d2d7b2.bin +0 -0
- data/spec/bitcoin/fixtures/rawtx-b5d4e8883533f99e5903ea2cf001a133a322fa6b1370b18a16c57c946a40823d.bin +0 -0
- data/spec/bitcoin/fixtures/rawtx-ba1ff5cd66713133c062a871a8adab92416f1e38d17786b2bf56ac5f6ffdfdf5.json +37 -0
- data/spec/bitcoin/fixtures/rawtx-c99c49da4c38af669dea436d3e73780dfdb6c1ecf9958baa52960e8baee30e73.json +24 -0
- data/spec/bitcoin/fixtures/rawtx-de35d060663750b3975b7997bde7fb76307cec5b270d12fcd9c4ad98b279c28c.json +23 -0
- data/spec/bitcoin/fixtures/rawtx-f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16.bin +0 -0
- data/spec/bitcoin/fixtures/rawtx-testnet-a220adf1902c46a39db25a24bc4178b6a88440f977a7e2cabfdd8b5c1dd35cfb.json +27 -0
- data/spec/bitcoin/fixtures/rawtx-testnet-e232e0055dbdca88bbaa79458683195a0b7c17c5b6c524a8d146721d4d4d652f.bin +0 -0
- data/spec/bitcoin/fixtures/rawtx-testnet-e232e0055dbdca88bbaa79458683195a0b7c17c5b6c524a8d146721d4d4d652f.json +41 -0
- data/spec/bitcoin/fixtures/reorg/blk_0_to_4.dat +0 -0
- data/spec/bitcoin/fixtures/reorg/blk_3A.dat +0 -0
- data/spec/bitcoin/fixtures/reorg/blk_4A.dat +0 -0
- data/spec/bitcoin/fixtures/reorg/blk_5A.dat +0 -0
- data/spec/bitcoin/fixtures/testnet/block_0.bin +0 -0
- data/spec/bitcoin/fixtures/testnet/block_1.bin +0 -0
- data/spec/bitcoin/fixtures/testnet/block_2.bin +0 -0
- data/spec/bitcoin/fixtures/testnet/block_3.bin +0 -0
- data/spec/bitcoin/fixtures/testnet/block_4.bin +0 -0
- data/spec/bitcoin/fixtures/testnet/block_5.bin +0 -0
- data/spec/bitcoin/fixtures/txdp-1.txt +32 -0
- data/spec/bitcoin/fixtures/txdp-2-signed.txt +19 -0
- data/spec/bitcoin/fixtures/txdp-2-unsigned.txt +14 -0
- data/spec/bitcoin/key_spec.rb +123 -0
- data/spec/bitcoin/network_spec.rb +48 -0
- data/spec/bitcoin/protocol/addr_spec.rb +68 -0
- data/spec/bitcoin/protocol/alert_spec.rb +20 -0
- data/spec/bitcoin/protocol/block_spec.rb +101 -0
- data/spec/bitcoin/protocol/inv_spec.rb +124 -0
- data/spec/bitcoin/protocol/ping_spec.rb +49 -0
- data/spec/bitcoin/protocol/tx_spec.rb +226 -0
- data/spec/bitcoin/protocol/version_spec.rb +77 -0
- data/spec/bitcoin/reorg_spec.rb +129 -0
- data/spec/bitcoin/script/opcodes_spec.rb +417 -0
- data/spec/bitcoin/script/script_spec.rb +246 -0
- data/spec/bitcoin/spec_helper.rb +36 -0
- data/spec/bitcoin/storage_spec.rb +229 -0
- data/spec/bitcoin/wallet/coinselector_spec.rb +35 -0
- data/spec/bitcoin/wallet/keygenerator_spec.rb +64 -0
- data/spec/bitcoin/wallet/keystore_spec.rb +188 -0
- data/spec/bitcoin/wallet/txdp_spec.rb +74 -0
- data/spec/bitcoin/wallet/wallet_spec.rb +207 -0
- metadata +295 -0
@@ -0,0 +1,290 @@
|
|
1
|
+
require_relative "em_gtk.rb"
|
2
|
+
require_relative "helpers.rb"
|
3
|
+
require_relative "tree_view.rb"
|
4
|
+
require_relative "addr_view.rb"
|
5
|
+
require_relative "tx_view.rb"
|
6
|
+
require_relative "conn_view.rb"
|
7
|
+
|
8
|
+
Gtk.init
|
9
|
+
|
10
|
+
module Bitcoin::Gui
|
11
|
+
|
12
|
+
class Gui
|
13
|
+
|
14
|
+
include Helpers
|
15
|
+
|
16
|
+
attr_reader :builder, :wallet, :storage
|
17
|
+
attr_accessor :node, :addr_view, :conn_view
|
18
|
+
|
19
|
+
def initialize storage, wallet_file = nil
|
20
|
+
@storage = storage
|
21
|
+
@node = nil
|
22
|
+
build
|
23
|
+
@addr_view = AddrView.new(self)
|
24
|
+
@tx_view = TxView.new(self, :tx_view)
|
25
|
+
@conn_view = ConnView.new(self)
|
26
|
+
open_wallet(wallet_file) if wallet_file
|
27
|
+
main_window.show_all
|
28
|
+
# notebook.next_page
|
29
|
+
statusicon.tooltip_text = "Bitcoin-Ruby GUI"
|
30
|
+
GObject.signal_connect(statusicon, "activate") do
|
31
|
+
main_window.visible ? main_window.hide : main_window.show
|
32
|
+
end
|
33
|
+
GObject.signal_connect(statusicon, "popup-menu") do
|
34
|
+
popup_menu.popup_for_device(nil, nil, nil, ->(*a) {}, nil, nil, 0, 0)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def log
|
39
|
+
@log ||= Bitcoin::Logger.create(:gui)
|
40
|
+
end
|
41
|
+
|
42
|
+
def build
|
43
|
+
@builder = Gtk::Builder.new
|
44
|
+
@builder.add_from_file(File.join(File.dirname(__FILE__), "gui.builder"))
|
45
|
+
@builder.connect_signals_full(->(builder, widget, signal, handler, _, _, gui) do
|
46
|
+
GObject.signal_connect(widget, signal) { gui.send(handler) }
|
47
|
+
end, self)
|
48
|
+
|
49
|
+
{
|
50
|
+
"<Control>n" => :file_new,
|
51
|
+
"<Control>o" => :file_open,
|
52
|
+
"<Control>q" => :file_quit,
|
53
|
+
"<Control>c" => :edit_copy,
|
54
|
+
"<Control>v" => :edit_paste,
|
55
|
+
"<Control>p" => :edit_preferences,
|
56
|
+
"<Control>h" => :help_about,
|
57
|
+
}.each do |binding, action|
|
58
|
+
send("menu_#{action}").add_accelerator("activate", accelgroup1,
|
59
|
+
*Gtk.accelerator_parse(binding), 0)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def update_wallet_views
|
64
|
+
return unless @wallet
|
65
|
+
EM.defer do
|
66
|
+
@addr_view.update(@wallet.keystore.keys)#.select{|k| k[:mine] == true})
|
67
|
+
@tx_view.update(@wallet.get_txouts(true))
|
68
|
+
unconfirmed = check_unconfirmed.active
|
69
|
+
wallet_text = "wallet: #{@wallet.keystore.config[:file]} | " +
|
70
|
+
"addresses: #{@wallet.addrs.size} | " +
|
71
|
+
"balance: #{"%.8f" % (@wallet.get_balance(unconfirmed) / 1e8)}"
|
72
|
+
status_wallet.push 0, wallet_text
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
def on_check_unconfirmed_toggled
|
77
|
+
update_wallet_views
|
78
|
+
end
|
79
|
+
|
80
|
+
def on_preferences
|
81
|
+
dialog(:preferences, :setup => ->(d) {
|
82
|
+
config = Bitcoin::Config.load({}, :wallet)
|
83
|
+
model = preferences_box_network.get_model
|
84
|
+
row = model.append
|
85
|
+
model.set_value(row, 0, "foo")
|
86
|
+
preferences_box_network.set_model model
|
87
|
+
# preferences_entry_network.text = config[:network]
|
88
|
+
}) do |*a|
|
89
|
+
p a
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
def on_copy_addr
|
94
|
+
addrs = []
|
95
|
+
valid, i = @addr_view.model.get_iter_first
|
96
|
+
while valid
|
97
|
+
if @addr_view.view.selection.iter_is_selected(i)
|
98
|
+
a = @addr_view.model.get_value(i, 0).get_string
|
99
|
+
addrs << a
|
100
|
+
end
|
101
|
+
valid = @addr_view.model.iter_next(i.to_ptr)
|
102
|
+
end
|
103
|
+
|
104
|
+
return unless addrs.any?
|
105
|
+
text = addrs.join(", ")
|
106
|
+
c = Gtk::Clipboard.get(Gdk::Atom.intern("PRIMARY", false))
|
107
|
+
c.set_text text, text.size
|
108
|
+
c = Gtk::Clipboard.get(Gdk::Atom.intern("CLIPBOARD", false))
|
109
|
+
c.set_text text, text.size
|
110
|
+
end
|
111
|
+
|
112
|
+
def on_paste_addr
|
113
|
+
c = Gtk::Clipboard.get(Gdk::Atom.intern("PRIMARY", false))
|
114
|
+
c.request_text ->(clip, text, data) {
|
115
|
+
on_new_addr(text) if Bitcoin.valid_address?(text)
|
116
|
+
}, self
|
117
|
+
c = Gtk::Clipboard.get(Gdk::Atom.intern("CLIPBOARD", false))
|
118
|
+
c.request_text ->(clip, text, data) {
|
119
|
+
on_new_addr(text) if Bitcoin.valid_address?(text)
|
120
|
+
}, self
|
121
|
+
end
|
122
|
+
|
123
|
+
def on_new_addr addr = nil
|
124
|
+
dialog(:new_addr, setup: ->(d) {
|
125
|
+
new_addr_check_addr.active = addr ? true : false
|
126
|
+
new_addr_check_pubkey.active = false
|
127
|
+
new_addr_check_mine.active = addr ? false : true
|
128
|
+
new_addr_entry_label.text = ""
|
129
|
+
new_addr_entry_addr.text = addr ? addr : ""
|
130
|
+
new_addr_entry_pubkey.text = ""
|
131
|
+
new_addr_entry_addr.hide unless addr
|
132
|
+
new_addr_entry_pubkey.hide
|
133
|
+
GObject.signal_connect(new_addr_check_addr, "toggled") do
|
134
|
+
new_addr_check_addr.active ? new_addr_entry_addr.show :
|
135
|
+
new_addr_entry_addr.hide
|
136
|
+
end
|
137
|
+
GObject.signal_connect(new_addr_check_pubkey, "toggled") do
|
138
|
+
new_addr_check_pubkey.active ? new_addr_entry_pubkey.show :
|
139
|
+
new_addr_entry_pubkey.hide
|
140
|
+
end
|
141
|
+
}) do |*a|
|
142
|
+
begin
|
143
|
+
label = new_addr_entry_label.text
|
144
|
+
set_address = new_addr_check_addr.active
|
145
|
+
set_pubkey = new_addr_check_pubkey.active
|
146
|
+
is_mine = new_addr_check_mine.active
|
147
|
+
key = {:label => label}
|
148
|
+
if set_address
|
149
|
+
addr = new_addr_entry_addr.text
|
150
|
+
raise "Address #{addr} invalid" unless Bitcoin.valid_address?(addr)
|
151
|
+
key[:addr] = addr
|
152
|
+
end
|
153
|
+
if set_pubkey
|
154
|
+
k = Bitcoin::Key.new(nil, new_addr_entry_pubkey.text)
|
155
|
+
key[:key] = k
|
156
|
+
key[:addr] = k.addr
|
157
|
+
end
|
158
|
+
if !set_address && !set_pubkey
|
159
|
+
key[:key] = Bitcoin::Key.generate
|
160
|
+
key[:addr] = key[:key].addr
|
161
|
+
end
|
162
|
+
key[:mine] = is_mine
|
163
|
+
@wallet.add_key(key)
|
164
|
+
update_wallet_views
|
165
|
+
rescue
|
166
|
+
message(:error, "Error adding key", $!.message, [:ok]) do
|
167
|
+
new_addr_dialog.show
|
168
|
+
end
|
169
|
+
end
|
170
|
+
update_addr_store
|
171
|
+
end
|
172
|
+
end
|
173
|
+
|
174
|
+
def on_new_tx
|
175
|
+
dialog(:new_tx, :setup => ->(d) {
|
176
|
+
new_tx_entry_address.text = ""
|
177
|
+
new_tx_entry_amount.text = ""
|
178
|
+
model = Gtk::ListStore.new([GObject::TYPE_STRING, GObject::TYPE_STRING])
|
179
|
+
@wallet.keystore.keys.each do |key|
|
180
|
+
row = model.append
|
181
|
+
model.set_value(row, 0, key[:label] || "")
|
182
|
+
model.set_value(row, 1, key[:addr])
|
183
|
+
end
|
184
|
+
renderer = Gtk::CellRendererText.new
|
185
|
+
comp = Gtk::EntryCompletion.new_with_area(area)
|
186
|
+
comp.text_column = 1
|
187
|
+
comp.minimum_key_length = 1
|
188
|
+
comp.set_match_func(->(comp, text, iter, _) {
|
189
|
+
label = comp.get_model.value(iter, 0).get_string
|
190
|
+
addr = comp.get_model.value(iter, 1).get_string
|
191
|
+
!!(label =~ /#{text}/ || addr =~ /#{text}/)
|
192
|
+
}, nil, nil)
|
193
|
+
comp.area.pack_start renderer, false, false, false
|
194
|
+
comp.area.orientation = :vertical
|
195
|
+
comp.set_model model
|
196
|
+
renderer.set_padding 10, 0
|
197
|
+
renderer.foreground = "blue"
|
198
|
+
comp.set_cell_data_func(renderer, ->(layout, renderer, model, iter, data) {
|
199
|
+
renderer.text = model.get_value(iter, 0).get_string
|
200
|
+
}, nil, nil)
|
201
|
+
new_tx_entry_address.set_completion comp
|
202
|
+
# GObject.signal_connect(comp, "match-selected") do |comp, _, iter, _|
|
203
|
+
# addr = comp.get_model.get_value(iter, 1).get_string
|
204
|
+
# new_tx_entry_address.text = addr
|
205
|
+
# true
|
206
|
+
# end
|
207
|
+
}) do |dialog|
|
208
|
+
|
209
|
+
address = new_tx_entry_address.text
|
210
|
+
amount = new_tx_entry_amount.text
|
211
|
+
unless Bitcoin.valid_address?(address)
|
212
|
+
message(:error, "Invalid Address",
|
213
|
+
"Address #{address} is not a valid bitcoin address.", [:ok]) do
|
214
|
+
new_tx_dialog.show
|
215
|
+
end
|
216
|
+
next
|
217
|
+
end
|
218
|
+
unless amount =~ /[0-9]*\.[0-9]*/
|
219
|
+
message(:error, "Invalid Amount",
|
220
|
+
"Amount #{amount} can not be parsed. Please use \"0.0\" form.", [:ok]) do
|
221
|
+
new_tx_dialog.show
|
222
|
+
end
|
223
|
+
next
|
224
|
+
end
|
225
|
+
value = (amount.to_f * 1e8).to_i
|
226
|
+
unless value <= @wallet.get_balance
|
227
|
+
message(:error, "Insufficient Balance",
|
228
|
+
"Balance #{@wallet.get_balance} is not sufficient to spend #{value}.", [:ok]) do
|
229
|
+
dialog.show
|
230
|
+
end
|
231
|
+
next
|
232
|
+
end
|
233
|
+
message(:question, "Really send transaction?",
|
234
|
+
"Do you really want to send #{format_value value} to #{address}?", [:no, :yes]) do
|
235
|
+
tx = @wallet.new_tx([[:address, *[address], value]], 0.00)
|
236
|
+
# puts tx.to_json
|
237
|
+
if @node.request(:relay_tx, tx)
|
238
|
+
puts "Transaction #{tx} relayed."
|
239
|
+
end
|
240
|
+
end
|
241
|
+
end
|
242
|
+
end
|
243
|
+
|
244
|
+
def on_new_wallet
|
245
|
+
dialog(:wallet_save, :filters => :wallet, :callbacks => {
|
246
|
+
"update-preview" => method(:wallet_preview)}) do
|
247
|
+
file = wallet_save_dialog.filename.read_string
|
248
|
+
open_wallet(file) unless File.file?(file)
|
249
|
+
end
|
250
|
+
end
|
251
|
+
|
252
|
+
def on_open_wallet
|
253
|
+
dialog(:wallet_open, :filters => :wallet, :callbacks => {
|
254
|
+
"update-preview" => method(:wallet_preview)}) do
|
255
|
+
open_wallet(wallet_open_dialog.filename.read_string)
|
256
|
+
end
|
257
|
+
end
|
258
|
+
|
259
|
+
def on_about
|
260
|
+
dialog(:about)
|
261
|
+
end
|
262
|
+
|
263
|
+
def on_exit
|
264
|
+
puts "bye"
|
265
|
+
Gtk.main_quit
|
266
|
+
EM.stop
|
267
|
+
end
|
268
|
+
|
269
|
+
def open_wallet(file)
|
270
|
+
keystore = Bitcoin::Wallet::SimpleKeyStore.new(:file => file)
|
271
|
+
@wallet = Bitcoin::Wallet::Wallet.new(@storage, keystore,
|
272
|
+
Bitcoin::Wallet::SimpleCoinSelector)
|
273
|
+
@wallet.on_tx do |type, tx|
|
274
|
+
puts "#{type} transaction: #{tx.hash}"
|
275
|
+
update_wallet_views
|
276
|
+
value = tx.out.select {|out| (@wallet.addrs & out.get_addresses).any? }
|
277
|
+
.map(&:value).inject(:+)
|
278
|
+
title = "#{type.to_s.capitalize} transaction"
|
279
|
+
message = "#{tx.hash}\nValue: #{format_value(value)}"
|
280
|
+
Notify.notify title, message
|
281
|
+
end
|
282
|
+
update_wallet_views
|
283
|
+
rescue
|
284
|
+
message(:error, "Error loading wallet", $!.message, [:ok])
|
285
|
+
p $!
|
286
|
+
puts *$@
|
287
|
+
end
|
288
|
+
|
289
|
+
end
|
290
|
+
end
|
@@ -0,0 +1,113 @@
|
|
1
|
+
module Bitcoin::Gui::Helpers
|
2
|
+
|
3
|
+
def display_tx tx_hash
|
4
|
+
tx = @storage.get_tx(tx_hash)
|
5
|
+
dialog(:tx, setup: ->(d) {
|
6
|
+
tx_label_hash.text = tx.hash
|
7
|
+
tx_label_value.text = format_value(tx.out.map(&:value).inject(:+))
|
8
|
+
tx_label_confirmations.text = tx.confirmations.to_s
|
9
|
+
txin_view = Bitcoin::Gui::TxInView.new(self, :tx_txin_view)
|
10
|
+
# txin_view.update(tx.in)
|
11
|
+
})
|
12
|
+
end
|
13
|
+
|
14
|
+
def wallet_preview(dialog, *args)
|
15
|
+
filename = dialog.preview_filename
|
16
|
+
file = filename.read_string rescue nil
|
17
|
+
if file && File.file?(file)
|
18
|
+
keystore = Bitcoin::Wallet::SimpleKeyStore.new(:file => file)
|
19
|
+
wallet = Bitcoin::Wallet::Wallet.new(@storage, keystore,
|
20
|
+
Bitcoin::Wallet::SimpleCoinSelector)
|
21
|
+
preview = Gtk::Label.new "Keys: #{wallet.addrs.size}\n" +
|
22
|
+
"Balance:\n%.8f" % (wallet.get_balance / 1e8)
|
23
|
+
end
|
24
|
+
dialog.preview_widget = preview
|
25
|
+
end
|
26
|
+
|
27
|
+
def add_wallet_filters dialog
|
28
|
+
filter = Gtk::FileFilter.new
|
29
|
+
filter.name = "Wallet Files"
|
30
|
+
filter.add_pattern("*.json")
|
31
|
+
dialog.add_filter filter
|
32
|
+
|
33
|
+
filter = Gtk::FileFilter.new
|
34
|
+
filter.name = "All Files"
|
35
|
+
filter.add_pattern("*")
|
36
|
+
dialog.add_filter filter
|
37
|
+
end
|
38
|
+
|
39
|
+
def dialog name, opts = {}
|
40
|
+
@dialog_cb_ids ||= {}
|
41
|
+
ids = @dialog_cb_ids[name] || []
|
42
|
+
dialog = send("#{name}_dialog")
|
43
|
+
send("add_#{opts[:filters]}_filters", dialog) if opts[:filters]
|
44
|
+
opts[:setup].call(dialog) if opts[:setup]
|
45
|
+
while id = ids.shift
|
46
|
+
GObject.signal_handler_disconnect(dialog, id)
|
47
|
+
end
|
48
|
+
ids << GObject.signal_connect(dialog, "response") do |dialog, response, *data|
|
49
|
+
yield(dialog, *data) if response > 0
|
50
|
+
dialog.hide
|
51
|
+
end
|
52
|
+
if dialog.is_a?(Gtk::FileChooserDialog)
|
53
|
+
ids << GObject.signal_connect(dialog, "file-activated") do |dialog, *data|
|
54
|
+
yield(dialog, *data)
|
55
|
+
dialog.hide
|
56
|
+
end
|
57
|
+
end
|
58
|
+
(opts[:callbacks] || {}).each do |name, block|
|
59
|
+
ids << GObject.signal_connect(dialog, name) {|*a| block.call(*a) }
|
60
|
+
end
|
61
|
+
dialog.show
|
62
|
+
@dialog_cb_ids[name] = ids
|
63
|
+
rescue
|
64
|
+
p $!
|
65
|
+
puts *$@
|
66
|
+
end
|
67
|
+
|
68
|
+
def message(type, title, text, buttons)
|
69
|
+
dialog(:message, :setup => ->(dialog){
|
70
|
+
dialog.message_type = Gtk::MessageType.find(type.to_sym)
|
71
|
+
dialog.text = title
|
72
|
+
dialog.secondary_text = text
|
73
|
+
[:yes, :no, :ok].each do |n|
|
74
|
+
b = send("message_dialog_button_#{n}")
|
75
|
+
buttons.include?(n) ? b.show : b.hide
|
76
|
+
end
|
77
|
+
}) do |dialog|
|
78
|
+
yield(dialog) if block_given?
|
79
|
+
dialog.show_all
|
80
|
+
dialog.hide
|
81
|
+
end
|
82
|
+
rescue
|
83
|
+
p $!
|
84
|
+
end
|
85
|
+
|
86
|
+
def method_missing name, *args
|
87
|
+
@builder.get_object(name.to_s) rescue super(name, *args)
|
88
|
+
end
|
89
|
+
|
90
|
+
def format_value val
|
91
|
+
"%.8f" % (val / 1e8)
|
92
|
+
end
|
93
|
+
|
94
|
+
def format_address address, label = nil
|
95
|
+
"#{label} (#{address})"
|
96
|
+
end
|
97
|
+
|
98
|
+
def format_version ver
|
99
|
+
ver.insert(-3, '.') if ver.size > 2
|
100
|
+
ver.insert(-6, '.') if ver.size > 5
|
101
|
+
ver.insert(0, "0.") if ver.size <= 7
|
102
|
+
ver
|
103
|
+
end
|
104
|
+
|
105
|
+
def format_uptime started
|
106
|
+
uptime = Time.now.to_i - started
|
107
|
+
mm, ss = uptime.divmod(60) #=> [4515, 21]
|
108
|
+
hh, mm = mm.divmod(60) #=> [75, 15]
|
109
|
+
dd, hh = hh.divmod(24) #=> [3, 3]
|
110
|
+
"%02d:%02d:%02d:%02d" % [dd, hh, mm, ss]
|
111
|
+
end
|
112
|
+
|
113
|
+
end
|
@@ -0,0 +1,82 @@
|
|
1
|
+
module Bitcoin::Gui
|
2
|
+
class TreeView
|
3
|
+
|
4
|
+
include Helpers
|
5
|
+
|
6
|
+
attr_accessor :model, :view
|
7
|
+
|
8
|
+
def initialize gui, view_name, columns
|
9
|
+
@gui = gui
|
10
|
+
@view = @gui.builder.get_object(view_name.to_s)
|
11
|
+
@model = Gtk::TreeStore.new(columns.map{|c| c[0] })
|
12
|
+
@view.set_model @model
|
13
|
+
|
14
|
+
columns.each_with_index do |cdef, i|
|
15
|
+
type, label, cb = *cdef
|
16
|
+
next unless label
|
17
|
+
case type
|
18
|
+
when GObject::TYPE_BOOLEAN
|
19
|
+
renderer = Gtk::CellRendererToggle.new
|
20
|
+
col = tree_view_col(renderer, label, "active", i) do |*args|
|
21
|
+
method(cb).call(@model, i, args[1], args[3]) if cb
|
22
|
+
end
|
23
|
+
when GObject::TYPE_STRING, GObject::TYPE_INT
|
24
|
+
renderer = Gtk::CellRendererText.new
|
25
|
+
col = tree_view_col(renderer, label, "text", i) do |*args|
|
26
|
+
method(cb).call(@model, i, args[1], args[3]) if cb
|
27
|
+
end
|
28
|
+
end
|
29
|
+
@view.append_column(col)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def embed name
|
34
|
+
parent = @gui.builder.get_object(name.to_s).parent
|
35
|
+
parent.remove(parent.get_child)
|
36
|
+
parent.add(@view)
|
37
|
+
end
|
38
|
+
|
39
|
+
def tree_view_col renderer, title, key, val, &block
|
40
|
+
col = Gtk::TreeViewColumn.new
|
41
|
+
col.pack_start renderer, true
|
42
|
+
col.add_attribute renderer, key, val
|
43
|
+
col.set_title title
|
44
|
+
col.set_cell_data_func(renderer, block, nil, nil)
|
45
|
+
col
|
46
|
+
end
|
47
|
+
|
48
|
+
def format_address_col model, i, renderer, iter
|
49
|
+
address = model.get_value(iter, i).get_string
|
50
|
+
label = model.get_value(iter, i+1).get_string
|
51
|
+
renderer.text = format_address(address, label)
|
52
|
+
end
|
53
|
+
|
54
|
+
def format_value_col model, i, renderer, iter
|
55
|
+
val = model.get_value(iter, i).get_string.to_i
|
56
|
+
renderer.text = format_value(val)
|
57
|
+
if val > 0
|
58
|
+
renderer.foreground = "darkgreen"
|
59
|
+
elsif val < 0
|
60
|
+
renderer.foreground = "red"
|
61
|
+
else
|
62
|
+
renderer.foreground = "black"
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def format_version_col model, i, renderer, iter
|
67
|
+
ver = model.get_value(iter, i).get_int.to_s
|
68
|
+
renderer.text = format_version(ver)
|
69
|
+
end
|
70
|
+
|
71
|
+
def format_uptime_col model, i, renderer, iter
|
72
|
+
started = model.get_value(iter, i).get_int
|
73
|
+
renderer.text = format_uptime(started)
|
74
|
+
end
|
75
|
+
|
76
|
+
def format_bool_col model, i, renderer, iter
|
77
|
+
active = model.get_value(iter, i).get_boolean
|
78
|
+
renderer.set_active active
|
79
|
+
end
|
80
|
+
|
81
|
+
end
|
82
|
+
end
|