bc 0.1

Sign up to get free protection for your applications and to get access to all the features.
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