bc 0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (6) hide show
  1. data.tar.gz.sig +1 -0
  2. data/README.md +8 -0
  3. data/UNLICENSE +24 -0
  4. data/lib/bc.rb +820 -0
  5. metadata +100 -0
  6. metadata.gz.sig +2 -0
data.tar.gz.sig ADDED
@@ -0,0 +1 @@
1
+ (s����$�y/�駇��DXQn�"YzIӚ>��`����4��ҽ���揫���D�
data/README.md ADDED
@@ -0,0 +1,8 @@
1
+ bc
2
+ ==
3
+
4
+ bc is a Ruby interface to bitcoind that tries to make interaction as easy as
5
+ possible.
6
+
7
+ P.S. Even though this README is pretty sparse, bc is not. So try it out, and the
8
+ README will be improved soon.
data/UNLICENSE ADDED
@@ -0,0 +1,24 @@
1
+ This is free and unencumbered software released into the public domain.
2
+
3
+ Anyone is free to copy, modify, publish, use, compile, sell, or
4
+ distribute this software, either in source code form or as a compiled
5
+ binary, for any purpose, commercial or non-commercial, and by any
6
+ means.
7
+
8
+ In jurisdictions that recognize copyright laws, the author or authors
9
+ of this software dedicate any and all copyright interest in the
10
+ software to the public domain. We make this dedication for the benefit
11
+ of the public at large and to the detriment of our heirs and
12
+ successors. We intend this dedication to be an overt act of
13
+ relinquishment in perpetuity of all present and future rights to this
14
+ software under copyright law.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
19
+ IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
20
+ OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
21
+ ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
22
+ OTHER DEALINGS IN THE SOFTWARE.
23
+
24
+ For more information, please refer to <http://unlicense.org/>
data/lib/bc.rb ADDED
@@ -0,0 +1,820 @@
1
+ # encoding: utf-8
2
+ require 'jr'
3
+
4
+ module Bitcoin
5
+ # Errors that the bitcoind is expected to raise should derive from this class.
6
+ class Error < Exception
7
+ end
8
+
9
+ # InvalidAddress is raised when a bitcoin address is invalid.
10
+ class InvalidAddress < Error
11
+ # This is the address that was invalid.
12
+ attr_reader :address
13
+
14
+ def initialize(address)
15
+ @address = address.freeze
16
+ super("Invalid bitcoin address: #{@address.inspect}")
17
+ end
18
+ end
19
+
20
+ # UnknownPrivateKey is raised when we need, but don't have, the private key
21
+ # associated with a bitcoin address.
22
+ class UnknownPrivateKey < InvalidAddress
23
+ end
24
+
25
+ # UnknownBlock is raised when bitcoind doesn't know about a given block.
26
+ class UnknownBlock < Error
27
+ # This is the block ID that bitcoind didn't know about.
28
+ attr_reader :block_id
29
+
30
+ def initialize(block_id)
31
+ @block_id = block_id.freeze
32
+ super("Unknown block ID: #{@block_id.inspect}")
33
+ end
34
+ end
35
+
36
+ # UnknownTransaction is raised when bitcoind doesn't know about a given
37
+ # transaction.
38
+ class UnknownTransaction < Error
39
+ # This is the transaction ID that bitcoind didn't know about.
40
+ attr_reader :transaction_id
41
+
42
+ def initialize(transaction_id)
43
+ @transaction_id = transaction_id.freeze
44
+ super("Unknown transaction ID: #{@transaction_id.inspect}")
45
+ end
46
+ end
47
+
48
+ # InsufficientFunds is raised when an account (or the bitcoind as a whole)
49
+ # doesn't have enough Bitcoin to perform an action.
50
+ class InsufficientFunds < Error
51
+ # This Float is the amount we would need to perform the action.
52
+ attr_reader :required
53
+
54
+ # This Float is the actual funds we have available to perform the action.
55
+ attr_reader :available
56
+
57
+ def initialize(required, available)
58
+ super("฿#{required} required, but only #{available} available")
59
+ end
60
+ end
61
+
62
+ # LockedWallet is raised when bitcoind refuses to send funds because the
63
+ # wallet is locked.
64
+ class LockedWallet < Error
65
+ def initialize()
66
+ super('You must unlock your wallet prior to sending funds.')
67
+ end
68
+ end
69
+
70
+ # InvalidPassphrase is raised when we attempt to decrypt the wallet with an
71
+ # invalid passphrase.
72
+ class InvalidPassphrase < Error
73
+ # This is the password we tried (unsuccessfully) to authenticate with.
74
+ attr_reader :bad_password
75
+
76
+ def initialize(bad_password)
77
+ @bad_password = bad_password
78
+ super("Invalid password: #{@bad_password.inspect}")
79
+ end
80
+ end
81
+
82
+ # This class represents a block in the Bitcoin block chain. (c.f.
83
+ # https://en.bitcoin.it/wiki/Block and
84
+ # https://en.bitcoin.it/wiki/Block_hashing_algorithm)
85
+ class Block
86
+ # This is the Bitcoin::Client instance we are connected to.
87
+ attr_reader :bc
88
+
89
+ # This is the unique ID of the block. It is a String beginning with a number
90
+ # of '0's.
91
+ attr_reader :block_id
92
+
93
+ # This is the order of this block in the block chain, i.e. the number of
94
+ # blocks that came before it.
95
+ attr_reader :height
96
+
97
+ # This is the block version. Currently, it should be +1+.
98
+ attr_reader :version
99
+
100
+ # This is a base 16 String representation of a SHA-256 hash generated from
101
+ # all the transactions in the block.
102
+ attr_reader :merkle_root
103
+
104
+ # This is a 32-bit Fixnum used in order to get a @block_id matching @target.
105
+ attr_reader :nonce
106
+
107
+ # This is a Float representing how difficult it was to generate the block.
108
+ # It increases logarithmically in proportion to difficulty.
109
+ attr_reader :difficulty
110
+
111
+ # This is an Array of every Transaction that is part of the block.
112
+ attr_reader :transactions
113
+
114
+ # This is a compact representation of the prefix this block was attempting
115
+ # to match.
116
+ attr_reader :target
117
+
118
+ # +bc+ is a Bitcoin::Client instance. block_id is our unique (String) block
119
+ # ID. We raise UnknownBitcoinBlock if the bitcoind doesn't know about a
120
+ # block with that ID.
121
+ def initialize(bc, block_id)
122
+ @bc = bc
123
+
124
+ unless @bc.is_a?(Bitcoin::Client)
125
+ raise TypeError, "bc must be a Bitcoin::Client (#{@bc.class} given)"
126
+ end
127
+
128
+ unless block_id.is_a?(String)
129
+ raise TypeError, "block_id must be a String (#{block_id.class} given)"
130
+ end
131
+
132
+ begin
133
+ block_data = @bc.jr.getblock(block_id)
134
+ rescue Jr::ServerError => ex
135
+ if ex.code == -5
136
+ raise UnknownBlock, block_id
137
+ else
138
+ raise
139
+ end
140
+ end
141
+
142
+ {
143
+ block_id: :hash,
144
+ height: :height,
145
+ version: :version,
146
+ merkle_root: :merkleroot,
147
+ created_at_unix_time: :time,
148
+ nonce: :nonce,
149
+ difficulty: :difficulty,
150
+ transaction_ids: :tx,
151
+ previous_block_id: :nextblockhash,
152
+ next_block_id: :previoushblockhash
153
+ }.each do |our_attr, block_data_key|
154
+ instance_variable_set(
155
+ "@#{our_attr}",
156
+ block_data[block_data_key.to_s].freeze
157
+ )
158
+ end
159
+ end
160
+
161
+ # This is the Block created immediately after this one, or +nil+ if this is
162
+ # the most recent block.
163
+ def next_block
164
+ @bc.get_block(@next_block_id)
165
+ end
166
+
167
+ # This is the Block created prior to this one, or +nil+ if this is the
168
+ # origin block.
169
+ def previous_block
170
+ @bc.get_block(@previous_block_id)
171
+ end
172
+
173
+ # This is the Time the block was created at.
174
+ def created_at
175
+ @created_at ||= Time.at(@created_at_unix_time).utc.freeze
176
+ end
177
+
178
+ def inspect
179
+ "#<Bitcoin::Block #{@block_id}>"
180
+ end
181
+ end
182
+
183
+ # This represents a single Bitcoin transaction.
184
+ class Transaction
185
+ # This is the Bitcoin::Client instance we're connected to.
186
+ attr_reader :bc
187
+
188
+ # This (String) is a unique identifier assigned to this transaction.
189
+ attr_reader :transaction_id
190
+
191
+ # This is a Hash whose keys are the (String) bitcoin addresses involved in
192
+ # the transaction (whose private keys bitcoind has) and whose values are the
193
+ # (Float) amounts that the corresponding address gained (in which case the
194
+ # value would be positive) or lost (in which case the value would be
195
+ # negative).
196
+ attr_reader :amounts
197
+
198
+ # This is a Hash similar to @amounts, except that the values are the amounts
199
+ # each address paid in transaction fees. (c.f.
200
+ # https://en.bitcoin.it/wiki/Transaction_fees)
201
+ attr_reader :fees
202
+
203
+ # +bc+ is a Bitcoin::Client. (The String) +transaction_id+ is our unique
204
+ # transaction ID. If bitcoind doesn't know about a transaction with that ID,
205
+ # we raise UnknownTransaction. Note that bitcoind only knows about
206
+ # transactions involving private keys in our wallet even though information
207
+ # about other transactions is in the block chain.
208
+ def initialize(bc, transaction_id)
209
+ @bc = bc
210
+ @transaction_id = transaction_id.freeze
211
+
212
+ unless @bc.is_a?(Bitcoin::Client)
213
+ raise TypeError, "bc must be a Bitcoin::Client (#{@bc.class} given)"
214
+ end
215
+
216
+ begin
217
+ info = @bc.jr.gettransaction(transaction_id)
218
+ rescue Jr::ServerError => ex
219
+ if info.code == -5
220
+ raise UnknownTransaction, transaction_id
221
+ end
222
+ end
223
+
224
+ @unix_time = info.fetch('time')
225
+
226
+ @fees = Hash.new
227
+ @amounts = Hash.new
228
+
229
+ info.fetch('details').each do |detail|
230
+ address = detail.fetch('address').freeze
231
+
232
+ @amounts[address] = detail.fetch('amount').freeze
233
+
234
+ if detail['fee']
235
+ @fees[address] = detail['fee'].freeze
236
+ end
237
+ end
238
+
239
+ @fees.freeze
240
+ @amounts.freeze
241
+ end
242
+
243
+ # This is the Time the transaction was made.
244
+ def time
245
+ @time ||= Time.at(@unix_time).utc.freeze
246
+ end
247
+
248
+ # Does this transaction include +address+? (Note that this only works for
249
+ # addresses which we control.)
250
+ def include?(address)
251
+ address = address.to_s if address.is_a?(Address)
252
+ @fees.keys.include?(address) or @amounts.keys.include?(address)
253
+ end
254
+
255
+ def inspect
256
+ "#<Bitcoin::Transaction #{@transaction_id}>"
257
+ end
258
+ end
259
+
260
+ # This is a bitcoind account. (c.f.
261
+ # https://en.bitcoin.it/wiki/Accounts_explained)
262
+ class Account
263
+ # This is the Bitcoin::Client instance we are connected to.
264
+ attr_reader :bc
265
+
266
+ # This (String) is our account designation.
267
+ attr_reader :name
268
+
269
+ # +bc+ is a Bitcoin::Client instance. +account_name+ is the (String) name of
270
+ # the account we're associated with. +account_name+ may be +""+, in which
271
+ # case we represent the default account.
272
+ def initialize(bc, account_name)
273
+ @bc = bc
274
+ @name = account_name.freeze
275
+
276
+ unless @name.is_a?(String)
277
+ raise TypeError, "account_name must be a String (#{@name.class} given)"
278
+ end
279
+
280
+ unless @bc.is_a?(Bitcoin::Client)
281
+ raise TypeError, "bc must be a Bitcoin::Client (#{@bc.class} given)"
282
+ end
283
+ end
284
+
285
+ # Get every Transaction associated with this account.
286
+ def transactions
287
+ grab = 20
288
+ position = 0
289
+ transactions = Array.new
290
+
291
+ loop do
292
+ new_transactions = @bc.jr.listtransactions(@name, grab, position)
293
+ transactions += new_transactions.map{|tx| tx.fetch('txid')}
294
+
295
+ if new_transactions.length < grab
296
+ break
297
+ else
298
+ position += grab
299
+ end
300
+ end
301
+
302
+ transactions.uniq.map do |tx|
303
+ Transaction.new(@bc, tx)
304
+ end
305
+ end
306
+
307
+ # Fetch the balance of this account. Only deposits with at least
308
+ # +minimum_confirmations+ will be included in this total.
309
+ def balance(minimum_confirmations=1)
310
+ @bc.jr.getbalance(@name, minimum_confirmations)
311
+ end
312
+
313
+ # This is an Array of every Address associated with this account.
314
+ def addresses
315
+ @bc.jr.getaddressesbyaccount(@name).map(&@bc.method(:get_address))
316
+ end
317
+
318
+ # Get an unused Address associated with this account, or create one if one
319
+ # doesn't already exist.
320
+ def unused_address
321
+ @bc.get_address(@bc.jr.getaccountaddress(@name))
322
+ end
323
+
324
+ # Get a new Address associated with this account.
325
+ def new_address
326
+ @bc.get_address(@bc.jr.getnewaddress(@name))
327
+ end
328
+
329
+ # Send +amount+ Bitcoin to +dest+. +amount+ should be a positive real
330
+ # number; +dest+ can either be a (String) bitcoin address, or an Address
331
+ # instance. We return a Transaction.
332
+ def send(dest, amount)
333
+ dest = dest.to_s
334
+
335
+ begin
336
+ txid = @bc.jr.sendfrom(@name, dest, amount)
337
+ rescue Jr::ServerError => ex
338
+ case ex.code
339
+ when -13
340
+ raise LockedWallet
341
+
342
+ when -6
343
+ raise InsufficientFunds.new(amount, balance)
344
+
345
+ when -5
346
+ raise InvalidAddress, dest
347
+
348
+ else
349
+ raise
350
+ end
351
+ end
352
+
353
+ @bc.get_transaction(txid)
354
+ end
355
+
356
+ # +dests+ is a Hash whose keys are Address or String instances and whose
357
+ # values are positive real numbers. Each key is sent the amount of Bitcoin
358
+ # specified by its value. We return a Transaction.
359
+ def send_to_many(dests)
360
+ dests = Hash[
361
+ dests.map do |dest, amount|
362
+ [dest.to_s, amount]
363
+ end
364
+ ]
365
+
366
+ begin
367
+ txid = @bc.jr.sendmany(@name, dest, amount)
368
+ rescue Jr::ServerError => ex
369
+ case ex.code
370
+ when -13
371
+ raise LockedWallet
372
+
373
+ when -6
374
+ raise InsufficientFunds.new(amount.values.reduce(&:+), balance)
375
+
376
+ when -5
377
+ raise InvalidAddress, ex.split(':').fetch(1)
378
+
379
+ else
380
+ raise
381
+ end
382
+ end
383
+
384
+ @bc.get_transaction(txid)
385
+ end
386
+
387
+ # Donate +amount+ to katmagic (bc's author).
388
+ def donate(amount)
389
+ tx = send('1LzDffumxiCSh8wEpxWE8fUozb2LUTcL8L', amount)
390
+
391
+ if STDOUT.tty?
392
+ puts('katmagic l♥ves y♥u ♥♥dles!')
393
+ end
394
+
395
+ tx
396
+ end
397
+
398
+ def to_s # :nodoc:
399
+ @name
400
+ end
401
+
402
+ def inspect # :nodoc:
403
+ "#<Bitcoin::Account #{@name.inspect}>"
404
+ end
405
+ end
406
+
407
+ # This class represents a Bitcoin address.
408
+ class Address
409
+ # This is the Bitcoin::Client instance we are connected to.
410
+ attr_reader :bc
411
+
412
+ # This is our actual (String) Bitcoin address.
413
+ attr_reader :address
414
+
415
+ # +bc+ is a Bitcoin::Client instance. +address+ is a (String) Bitcoin
416
+ # address that we have the private key for.
417
+ def initialize(bc, address)
418
+ @bc = bc
419
+ @address = address
420
+
421
+ unless @bc.is_a?(Bitcoin::Client)
422
+ raise TypeError, "bc must be a Bitcoin::Client (#{@bc.class} given)"
423
+ end
424
+
425
+ unless valid?
426
+ raise InvalidAddress, @address
427
+ end
428
+ end
429
+
430
+ # Return an Array of every Transaction associated with us.
431
+ def transactions
432
+ account.transactions.find_all do |tx|
433
+ tx.include?(self)
434
+ end
435
+ end
436
+
437
+ # Are we a valid Bitcoin address?
438
+ def valid?
439
+ @bc.is_valid?(@address)
440
+ end
441
+
442
+ # This is the (String) private key associated with this address. If this
443
+ # account's address is invalid (or from the wrong bitcoin network), we raise
444
+ # InvalidAddress. If we don't have the private key associated with this
445
+ # address (or our wallet is locked), we return +nil+.
446
+ def private_key
447
+ begin
448
+ @private_key ||= @bc.jr.dumpprivkey(@address)
449
+ rescue Jr::ServerError => ex
450
+ $ex = ex.code
451
+ case ex.code
452
+ when -5
453
+ raise(InvalidAddress, @address)
454
+ when -4
455
+ nil
456
+ else
457
+ raise
458
+ end
459
+ end
460
+ end
461
+
462
+ # Get the Account we're associated with.
463
+ def account
464
+ @bc.get_account(@bc.jr.getaccount(@address))
465
+ end
466
+
467
+ # Associate us with +account+, which can either be a String or an Account.
468
+ # Either way, we will return an Account.
469
+ def account=(account)
470
+ @bc.jr.setaccount(@address, account.to_s)
471
+ @bc.get_account(account.to_s)
472
+ end
473
+
474
+ # Sign the (String) message +msg+. We return a detached base-64 encoded
475
+ # signature (String). In order to verify the message, you will need both the
476
+ # signature and +msg+. If we don't know private key, we raise
477
+ # UnknownPrivateKey. (c.f. Client.verify())
478
+ def sign(msg)
479
+ begin
480
+ @bc.jr.signmessage(@address, msg)
481
+ rescue Jr::ServerError => ex
482
+ case ex.code
483
+ when -13
484
+ raise LockedWallet
485
+
486
+ when -4
487
+ raise UnknownPrivateKey, @address
488
+
489
+ else
490
+ raise
491
+ end
492
+ end
493
+ end
494
+
495
+ # Verify the signature +sig+ of a message +msg+ signed by our private key.
496
+ # We return +true+ if the signature is valid, or +false+ if it is not.
497
+ def verify(msg, sig)
498
+ @bc.jr.verifymessage(@address, msg, sig)
499
+ end
500
+
501
+ def to_s # :nodoc:
502
+ @address
503
+ end
504
+
505
+ def inspect # :nodoc:
506
+ "#<Bitcoin::Address #{@address}>"
507
+ end
508
+ end
509
+
510
+ class Client
511
+ attr_reader :user, :password, :host, :port, :ssl
512
+ # @jr is a Jr::Jr instance connected to the bitcoind.
513
+ attr_reader :jr
514
+
515
+ def initialize(user, password, host='127.0.0.1', port=8331, ssl=false)
516
+ %w{user password host port ssl}.each do |var|
517
+ instance_variable_set("@#{var}", eval(var))
518
+ end
519
+
520
+ @jr = Jr::Jr.new(@host, @port, @user, @password, ssl)
521
+
522
+ @accounts = Hash.new
523
+ @addresses = Hash.new
524
+ @blocks = Hash.new
525
+ @transactions = Hash.new
526
+ end
527
+
528
+ # Get an Array of every Address we have.
529
+ def addresses
530
+ @jr.listreceivedbyaddress(0, true).map do |addr_info|
531
+ get_address(addr_info.fetch('address'))
532
+ end
533
+ end
534
+
535
+ # Get an Array of every Account we have.
536
+ def accounts
537
+ @jr.listreceivedbyaccount(0, true).map do |acct_info|
538
+ get_account(acct_info.fetch('account'))
539
+ end
540
+ end
541
+
542
+ # Get the Address +addr+. If +addr+ is invalid, we raise InvalidAddress. The
543
+ # result of this function is cached.
544
+ def get_address(addr)
545
+ @addresses[addr] ||= Address.new(self, addr)
546
+ end
547
+
548
+ # Get the account associated with the String +label+, or create it if it
549
+ # doesn't already exist. The result of this function is cached.
550
+ def get_account(label)
551
+ @accounts[label] ||= Account.new(self, label)
552
+ end
553
+ alias_method :[], :get_account
554
+
555
+ # Does +acct+ have any associated addresses?
556
+ def has_account?(acct)
557
+ !get_account(acct).addresses.empty?
558
+ end
559
+
560
+ # Get the Block with a hash of +block_id+, or if +block_id+ is a Fixnum, the
561
+ # Block with a height of +block_id+. If +block_id+ is an unknown block ID,
562
+ # we raise UnknownBlock+; if +block_id+ is a Fixnum and there is no
563
+ # associated block, we raise RangeError. The result of this function is
564
+ # cached.
565
+ def get_block(block_id)
566
+ if block_id.is_a?(Fixnum)
567
+ begin
568
+ block_id = @jr.getblockhash(block_id)
569
+ rescue Jr::ServerError => ex
570
+ if ex.code == -1
571
+ raise RangeError, "block_id #{block_id.inspect} is out of range."
572
+ else
573
+ raise
574
+ end
575
+ end
576
+ end
577
+
578
+ @blocks[block_id] ||= Block.new(self, block_id)
579
+ end
580
+
581
+ # Get the Transaction with the ID +transaction_id+. We raise
582
+ # UnknownTransaction if we don't know about a Transaction with that ID.
583
+ # The result of this function is cached.
584
+ def get_transaction(transaction_id)
585
+ if transaction_id.is_a?(Fixnum)
586
+ end
587
+
588
+ @transactions[transaction_id] ||= Transaction.new(self, transaction_id)
589
+ end
590
+
591
+ # Get an Array of every Transaction that has occurred since +block+. If
592
+ # +block+ is not a Block it will be converted into one with get_block().
593
+ def get_transactions_since(block)
594
+ unless block.is_a?(Block)
595
+ block = get_block(block)
596
+ end
597
+
598
+ @jr.listsinceblock(block.block_id).fetch('transactions').map do |tx|
599
+ tx.fetch('txid')
600
+ end.uniq.map(&method(:get_transaction))
601
+ end
602
+
603
+ # Get an Array of every Transaction involving one of our addresses.
604
+ def transactions
605
+ get_transactions_since(0)
606
+ end
607
+
608
+ # This is the latest Block we've processed.
609
+ def latest_block
610
+ get_block( @jr.getinfo().fetch('blocks') )
611
+ end
612
+
613
+ # Send +amount+ Bitcoin to +dest+. +amount+ should be a positive real
614
+ # number; +dest+ can either be a String bitcoin address, or an Address
615
+ # instance. We return a Transaction.
616
+ def send(dest, amount)
617
+ dest = dest.to_s
618
+
619
+ begin
620
+ txid = @jr.sendtoaddress(dest, amount)
621
+ rescue Jr::ServerError => ex
622
+ case ex.code
623
+ when -13
624
+ raise LockedWallet
625
+
626
+ when -6
627
+ raise InsufficientFunds.new(amount, balance)
628
+
629
+ when -5
630
+ raise InvalidAddress, dest
631
+
632
+ else
633
+ raise
634
+ end
635
+ end
636
+
637
+ get_transaction(txid)
638
+ end
639
+
640
+ # Donate +amount+ to katmagic (bc's author).
641
+ def donate(amount)
642
+ tx = send('1LzDffumxiCSh8wEpxWE8fUozb2LUTcL8L', amount)
643
+
644
+ if STDOUT.tty?
645
+ puts('katmagic l♥ves y♥u ♥♥dles!')
646
+ end
647
+
648
+ tx
649
+ end
650
+
651
+ # Safely copies our wallet to destination at +path+. If +path+ is a
652
+ # directory, we will copy our wallet to +path+/wallet.dat.
653
+ def backup_wallet(path)
654
+ @jr.backupwallet(path)
655
+ end
656
+
657
+ # Encrypt the wallet with +passwd+ and stop bitcoind.
658
+ def encrypt_wallet(passwd)
659
+ @jr.encryptwallet(passwd)
660
+ end
661
+
662
+ # Change the wallet's passphrase from +old_passwd+ to +new_passwd+.
663
+ def change_wallet_passwd(old_passwd, new_passwd)
664
+ begin
665
+ @jr.walletpassphrasechange(old_passwd, new_passwd)
666
+ rescue Jr::ServerError => ex
667
+ if ex.code == -14
668
+ raise InvalidPassphrase, passwd
669
+ else
670
+ raise
671
+ end
672
+ end
673
+ end
674
+
675
+ # Unlock the wallet with +passwd+ for +timeout+ seconds.
676
+ def unlock_wallet(passwd, timeout=300)
677
+ begin
678
+ @jr.walletpassphrase(passwd, timeout)
679
+ rescue Jr::ServerError => ex
680
+ if ex.code == -14
681
+ raise InvalidPassphrase, passwd
682
+ else
683
+ raise
684
+ end
685
+ end
686
+ end
687
+
688
+ # Lock the wallet.
689
+ def lock_wallet()
690
+ @jr.walletlock()
691
+ end
692
+
693
+ # Get the total balance of all our accounts. (This includes all transactions
694
+ # with at least 1 confirmation.)
695
+ def balance
696
+ @jr.getbalance()
697
+ end
698
+
699
+ # How many blocks are there (that we know about) in the block chain?
700
+ def block_count
701
+ @jr.getblockcount()
702
+ end
703
+
704
+ # How many peers are we connected to?
705
+ def connection_count
706
+ @jr.getconnectioncount()
707
+ end
708
+
709
+ # This is a Float representing the difficulty associated with finding the
710
+ # next block. The higher the number, the more difficult it is. (c.f.
711
+ # https://en.bitcoin.it/wiki/Difficulty)
712
+ def difficulty
713
+ @jr.getdifficulty()
714
+ end
715
+
716
+ # Are we trying to generate a block?
717
+ def generate?
718
+ @jr.getgenerate()
719
+ end
720
+
721
+ alias_method :generate, :generate?
722
+
723
+ # If +should_generate+ is +true+, we instruct bitcoind to begin (or
724
+ # continue) to generate a block. If it is +false+, we do the opposite.
725
+ def generate=(should_generate)
726
+ @jr.setgenerate(should_generate)
727
+ should_generate
728
+ end
729
+
730
+ # How many blocks are we hashing per second? This will be zero unless we're
731
+ # trying to generate a block.
732
+ def hashes_per_second
733
+ @jr.gethashespersec()
734
+ end
735
+
736
+ # This [Fixnum] is the version of bitcoind we're connecting to.
737
+ def bitcoind_version
738
+ @jr.getinfo().fetch('version')
739
+ end
740
+
741
+ # This (Fixnum) is the version of the Bitcoin RPC protocol we're
742
+ # communicating in.
743
+ def protocol_version
744
+ @jr.getinfo().fetch('protocolversion')
745
+ end
746
+
747
+ # This is the proxy bitcoind is using, or +nil+ if we're not using a proxy.
748
+ def proxy
749
+ @jr.getinfo().fetch('proxy')
750
+ end
751
+
752
+ # Is bitcoind using a proxy?
753
+ def proxy?
754
+ !!proxy
755
+ end
756
+
757
+ # Are we on the testnet?
758
+ def testnet?
759
+ @jr.getinfo().fetch('testnet')
760
+ end
761
+
762
+ # This is how much we're configured to use as our transaction fee. (c.f.
763
+ # https://en.bitcoin.it/wiki/Transaction_fees)
764
+ def transaction_fee
765
+ @jr.getinfo().fetch('paytxfee')
766
+ end
767
+
768
+ # This is the Time the oldest key in our key pool was created. (c.f.
769
+ # key_pool_size())
770
+ def oldest_key
771
+ Time.at(@jr.getinfo().fetch('keypoololdest'))
772
+ end
773
+
774
+ # This is the (Fixnum) size of our key pool. The key pool is a pre-generated
775
+ # set of Bitcoin keys which are then allocated through the other address
776
+ # allocation mechanisms. It exists so that backups will (hopefully) contain
777
+ # all the private keys we actually used.
778
+ def key_pool_size
779
+ @jr.getinfo().fetch('keypoolsize')
780
+ end
781
+
782
+ # This is a Hash containing data about the next block that will be generated
783
+ # (c.f. the documentation regarding the getmemorypool API call in
784
+ # https://en.bitcoin.it/wiki/Original_Bitcoin_client/API_Calls_list)
785
+ def memory_pool
786
+ @jr.getmemorypool()
787
+ end
788
+
789
+ # Import a Bitcoin private key. We will fail if the key already exists in
790
+ # our wallet. We return an Address. (c.f. get_private_key())
791
+ def import_private_key(key, label=nil)
792
+ label ||= ''
793
+ @jr.get_address( @jr.importprivkey(key, label) )
794
+ end
795
+
796
+ # Refill our key pool. (c.f. key_pool_size())
797
+ def refill_key_pool()
798
+ @jr.keypoolrefill()
799
+ end
800
+
801
+ # Set the transaction fee to +fee+. (c.f.
802
+ # https://en.bitcoin.it/wiki/Transaction_fees)
803
+ def transaction_fee=(fee)
804
+ fee = fee.to_f
805
+ @jr.settxfee(fee)
806
+ fee
807
+ end
808
+
809
+ # Stop bitcoind.
810
+ def stop()
811
+ @jr.stop()
812
+ end
813
+
814
+ # Is +addr+ a valid bitcoin address? If we're using the testnet, normal
815
+ # addresses won't be valid; if we're not, testnet addresses won't be valid.
816
+ def is_valid?(addr)
817
+ @jr.validateaddress(addr.to_s)['isvalid']
818
+ end
819
+ end
820
+ end
metadata ADDED
@@ -0,0 +1,100 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: bc
3
+ version: !ruby/object:Gem::Version
4
+ version: '0.1'
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - katmagic
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain:
12
+ - ! '-----BEGIN CERTIFICATE-----
13
+
14
+ MIIDQDCCAiigAwIBAgIBADANBgkqhkiG9w0BAQUFADBGMRgwFgYDVQQDDA90aGUu
15
+
16
+ bWFnaWNhbC5rYXQxFTATBgoJkiaJk/IsZAEZFgVnbWFpbDETMBEGCgmSJomT8ixk
17
+
18
+ ARkWA2NvbTAeFw0xMTA4MjEyMjMyMDFaFw0xMjA4MjAyMjMyMDFaMEYxGDAWBgNV
19
+
20
+ BAMMD3RoZS5tYWdpY2FsLmthdDEVMBMGCgmSJomT8ixkARkWBWdtYWlsMRMwEQYK
21
+
22
+ CZImiZPyLGQBGRYDY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA
23
+
24
+ pBt20nwjs5W03djpRN6FAbpiio286NHMTk6HhmjV6GZKOi5ZUX5onTnKUg2Vc35z
25
+
26
+ /nK+aIPReyRfBgIcfSjhoXh1A1Dp+2laNgTtU/3eMupruatgORAPCSaG9Ns+HSyR
27
+
28
+ vySbz1QUrwvlvF0qkhhApNQ6dsLl2LMOV3QcluY+Y3CVccOWOSHdQcnAbPuzM9Hf
29
+
30
+ 4ChI4OGL7+DwLA5OK2S5uewRAa2iLkJSN0WugnQlJqMT59GRaqTDOtnYQpiyKEBy
31
+
32
+ QjVPO4LNk7iDsJP22YBrveIzm8/YYRBTU4LTHMEMOyCszrYqD2S1Lwp2rtCJzQCl
33
+
34
+ BA0LtBKrZl5mwZm7qyj+TwIDAQABozkwNzAJBgNVHRMEAjAAMB0GA1UdDgQWBBSm
35
+
36
+ s5arhjp61kmGl6wsmLYkqerdqDALBgNVHQ8EBAMCBLAwDQYJKoZIhvcNAQEFBQAD
37
+
38
+ ggEBAA6cQNQMOPRy4yrj7Nh5Mb9qq8t/8ho/JQvjzVof9qRd+kfKrOoOhXfEO+Rm
39
+
40
+ sWcaOnBCVC4DnZuNDSLygVhCDtMnHjg/JsfO/GBF/QlNTJOO1jkoQiS6w0KARlBm
41
+
42
+ cpXaWg/oMtXJ2PaUga6WkNeXYf9Mad36P4yuGQScjs+WkUUy7DNZvTGReIcCWOR8
43
+
44
+ jteSvvCMobQKGr2DfFOU9Jiddh2FPpz/KOM2ijzwsVNUMUr7R58LoCnQZrZ/YaRW
45
+
46
+ ob6QnVgwqu5SUAKQxlFJ/aKlPMj735z8EogaZC1ZHgg3vkgGGyu57N/8BDDG0TzC
47
+
48
+ Zn3u2leVae/fJ03zYGArhuJKPgc=
49
+
50
+ -----END CERTIFICATE-----
51
+
52
+ '
53
+ date: 2012-03-05 00:00:00.000000000 Z
54
+ dependencies:
55
+ - !ruby/object:Gem::Dependency
56
+ name: jr
57
+ requirement: &76283090 !ruby/object:Gem::Requirement
58
+ none: false
59
+ requirements:
60
+ - - ! '>='
61
+ - !ruby/object:Gem::Version
62
+ version: '0'
63
+ type: :runtime
64
+ prerelease: false
65
+ version_requirements: *76283090
66
+ description: bc is a Ruby interface to bitcoind.
67
+ email: the.magical.kat@gmail.com
68
+ executables: []
69
+ extensions: []
70
+ extra_rdoc_files: []
71
+ files:
72
+ - lib/bc.rb
73
+ - README.md
74
+ - UNLICENSE
75
+ homepage: https://github.com/katmagic/bc
76
+ licenses: []
77
+ post_install_message:
78
+ rdoc_options: []
79
+ require_paths:
80
+ - lib
81
+ required_ruby_version: !ruby/object:Gem::Requirement
82
+ none: false
83
+ requirements:
84
+ - - ! '>='
85
+ - !ruby/object:Gem::Version
86
+ version: '0'
87
+ required_rubygems_version: !ruby/object:Gem::Requirement
88
+ none: false
89
+ requirements:
90
+ - - ! '>='
91
+ - !ruby/object:Gem::Version
92
+ version: '0'
93
+ requirements: []
94
+ rubyforge_project:
95
+ rubygems_version: 1.8.10
96
+ signing_key:
97
+ specification_version: 3
98
+ summary: Interface with bitcoid.
99
+ test_files: []
100
+ has_rdoc:
metadata.gz.sig ADDED
@@ -0,0 +1,2 @@
1
+ (!xX����|��?�n���]��3������O;)� )l�b#MH���yu�<?lU"�6��c�ܫ����K�������O X� k'�d�)�p�U#5g���D����u��a�=�OeO{�R��:iev��q�����}���䈭0p�^)�M�1���0z�5��.�X0L(I� g�����p9�������s�>���M�er�:���\~�
2
+ �;r?��ı��刏rD��j�eUa^^�� 5