bitbank 0.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 (38) hide show
  1. data/.autotest +23 -0
  2. data/.document +5 -0
  3. data/.rspec +2 -0
  4. data/Gemfile +23 -0
  5. data/Gemfile.lock +59 -0
  6. data/LICENSE.txt +20 -0
  7. data/README.rdoc +60 -0
  8. data/Rakefile +42 -0
  9. data/VERSION +1 -0
  10. data/bitbank.gemspec +112 -0
  11. data/config.yml +4 -0
  12. data/lib/bitbank.rb +47 -0
  13. data/lib/bitbank/account.rb +36 -0
  14. data/lib/bitbank/client.rb +58 -0
  15. data/lib/bitbank/transaction.rb +43 -0
  16. data/spec/account_spec.rb +64 -0
  17. data/spec/bitbank_spec.rb +37 -0
  18. data/spec/client_spec.rb +134 -0
  19. data/spec/fixtures/vcr_cassettes/account/address.yml +30 -0
  20. data/spec/fixtures/vcr_cassettes/account/balance.yml +30 -0
  21. data/spec/fixtures/vcr_cassettes/account/new_address.yml +30 -0
  22. data/spec/fixtures/vcr_cassettes/account/pay.yml +60 -0
  23. data/spec/fixtures/vcr_cassettes/account/transactions.yml +30 -0
  24. data/spec/fixtures/vcr_cassettes/client/accounts.yml +30 -0
  25. data/spec/fixtures/vcr_cassettes/client/balance.yml +30 -0
  26. data/spec/fixtures/vcr_cassettes/client/balance_account.yml +30 -0
  27. data/spec/fixtures/vcr_cassettes/client/block_count.yml +30 -0
  28. data/spec/fixtures/vcr_cassettes/client/difficulty.yml +30 -0
  29. data/spec/fixtures/vcr_cassettes/client/get_work.yml +30 -0
  30. data/spec/fixtures/vcr_cassettes/client/info.yml +30 -0
  31. data/spec/fixtures/vcr_cassettes/client/new_address.yml +30 -0
  32. data/spec/fixtures/vcr_cassettes/client/transactions.yml +30 -0
  33. data/spec/fixtures/vcr_cassettes/client/transactions_account.yml +30 -0
  34. data/spec/fixtures/vcr_cassettes/transaction/details.yml +30 -0
  35. data/spec/spec_helper.rb +22 -0
  36. data/spec/support/focused.rb +7 -0
  37. data/spec/transaction_spec.rb +73 -0
  38. metadata +232 -0
@@ -0,0 +1,58 @@
1
+ module Bitbank
2
+ class Client
3
+ def initialize(config={})
4
+ @config = config
5
+ @endpoint = "http://#{config[:username]}:#{config[:password]}" +
6
+ "@#{config[:host]}:#{config[:port]}"
7
+ end
8
+
9
+ def accounts
10
+ account_data = request('listaccounts')
11
+ account_data.map do |account_name, account_value|
12
+ Account.new(self, account_name, account_value)
13
+ end
14
+ end
15
+
16
+ def balance(account_name=nil)
17
+ request('getbalance', account_name)
18
+ end
19
+
20
+ # Returns the number of blocks in the longest block chain.
21
+ def block_count
22
+ request('getblockcount')
23
+ end
24
+
25
+ # Returns the proof-of-work difficulty as a multiple of the minimum difficulty.
26
+ def difficulty
27
+ request('getdifficulty')
28
+ end
29
+
30
+ def get_work(data=nil)
31
+ request('getwork', data)
32
+ end
33
+
34
+ def info
35
+ request('getinfo')
36
+ end
37
+
38
+ def new_address(account_name=nil)
39
+ request('getnewaddress', account_name)
40
+ end
41
+
42
+ def transactions(account_name=nil, count=10)
43
+ transaction_data = request('listtransactions', account_name, count)
44
+ transaction_data.map do |txdata|
45
+ Transaction.new(self, txdata['txid'], txdata)
46
+ end
47
+ end
48
+
49
+ def request(method, *args)
50
+ body = { 'id' => 'jsonrpc', 'method' => method }
51
+ body['params'] = args unless args.empty? || args.first.nil?
52
+
53
+ response_json = RestClient.post(@endpoint, body.to_json)
54
+ response = JSON.parse(response_json)
55
+ response['result']
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,43 @@
1
+ module Bitbank
2
+ class Transaction
3
+ attr_reader :txid, :address, :category, :amount, :confirmations
4
+
5
+ def initialize(client, txid, data={})
6
+ @client = client
7
+ @txid = txid
8
+
9
+ load_details(data)
10
+ end
11
+
12
+ def account
13
+ @account ? Account.new(@client, @account) : nil
14
+ end
15
+
16
+ def time
17
+ Time.at(@time)
18
+ end
19
+
20
+ def confirmed?
21
+ confirmations && confirmations > 6
22
+ end
23
+
24
+ def ==(other)
25
+ txid == other.txid
26
+ end
27
+
28
+ private
29
+
30
+ def load_details(data={})
31
+ data = @client.request('gettransaction', txid) if data.empty?
32
+ data.symbolize_keys!
33
+
34
+ details = ((data.delete(:details) || []).first || {}).symbolize_keys
35
+ @account = data[:account] || details[:account]
36
+ @address = data[:address] || details[:address]
37
+ @category = data[:category] || details[:category]
38
+ @amount = data[:amount] || details[:amount]
39
+ @confirmations = data[:confirmations]
40
+ @time = data[:time]
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,64 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
+
3
+ describe "Bitbank::Account" do
4
+ before(:each) do
5
+ @client = Bitbank.new(File.join(File.dirname(__FILE__), '..', 'config.yml'))
6
+ @account = Bitbank::Account.new(@client, 'adent', 0.02)
7
+ end
8
+
9
+ it 'should have a name' do
10
+ @account.name.should == 'adent'
11
+ end
12
+
13
+ describe 'address' do
14
+ use_vcr_cassette 'account/address'
15
+
16
+ it 'should retrieve the address for this account' do
17
+ @account.address.should == '1NqwGDRi9Gs4xm1BmPnGeMwgz1CowP6CeQ'
18
+ end
19
+ end
20
+
21
+ describe 'balance' do
22
+ use_vcr_cassette 'account/balance'
23
+
24
+ it 'should retrieve the current balance' do
25
+ @account.balance.should == 0.02
26
+ end
27
+ end
28
+
29
+ describe 'new_address' do
30
+ use_vcr_cassette 'account/new_address'
31
+
32
+ it 'should create a new address associated with this account and return it' do
33
+ @account.new_address.should == '15GsE7o3isyQ7ygzh8Cya58oetrGYygdoi'
34
+ end
35
+ end
36
+
37
+ describe 'pay' do
38
+ use_vcr_cassette 'account/pay'
39
+
40
+ it 'should return a new transaction' do
41
+ transaction = @account.pay('destinationaddress', 0.01)
42
+ transaction.amount.should == 0.01
43
+ transaction.account.should == @account
44
+ end
45
+ end
46
+
47
+ describe 'transactions' do
48
+ use_vcr_cassette 'account/transactions'
49
+
50
+ it 'should retrieve transactions for this account' do
51
+ transactions = @account.transactions
52
+ transactions.length.should == 1
53
+ transactions.first.is_a?(Bitbank::Transaction).should be_true
54
+ transactions.first.txid.should == 'txid1'
55
+ end
56
+ end
57
+
58
+ describe 'equality' do
59
+ it 'should compare account names' do
60
+ @account.should_not == Bitbank::Account.new(@client, 'prefect')
61
+ @account.should == Bitbank::Account.new(@client, 'adent')
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,37 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
+
3
+ describe "Bitbank" do
4
+ describe 'initialization' do
5
+ it 'should configure the client' do
6
+ client = Bitbank.new(:username => 'prefect', :password => 'hoopy')
7
+ client.is_a?(Bitbank::Client).should be_true
8
+ end
9
+ end
10
+
11
+ describe 'configuration' do
12
+ it 'should require a username and password' do
13
+ expect {
14
+ Bitbank.config = { :username => 'marvin' }
15
+ }.to raise_error(ArgumentError, 'Please specify a username and password for bitcoind')
16
+ end
17
+
18
+ it 'should parse a yaml file for credentials' do
19
+ Bitbank.config = File.join(File.dirname(__FILE__), '..', 'config.yml')
20
+ Bitbank.config[:username].should == 'testuser'
21
+ Bitbank.config[:password].should == 'testpass'
22
+ end
23
+
24
+ it 'should use default values' do
25
+ Bitbank.config = File.join(File.dirname(__FILE__), '..', 'config.yml')
26
+ Bitbank.config[:host].should == 'localhost'
27
+ Bitbank.config[:port].should == 8332
28
+ end
29
+ end
30
+
31
+ describe 'version' do
32
+ it 'should read version from file' do
33
+ Bitbank.version.should == File.read(
34
+ File.join(File.dirname(__FILE__), '..', 'VERSION')).chomp
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,134 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
+
3
+ describe "Bitbank::Client" do
4
+ before(:each) do
5
+ @client = Bitbank.new(File.join(File.dirname(__FILE__), '..', 'config.yml'))
6
+ end
7
+
8
+ describe 'request' do
9
+ before(:each) do
10
+ @json = '{"result":{"foo":"bar"}}'
11
+ end
12
+
13
+ it 'should post to the endpoint' do
14
+ RestClient.expects(:post).with(
15
+ @client.instance_variable_get('@endpoint'), anything).returns(@json)
16
+ @client.request('foo')
17
+ end
18
+
19
+ it 'should parse json results' do
20
+ RestClient.stubs(:post).returns(@json)
21
+ @client.request('whatever').should == { 'foo' => 'bar' }
22
+ end
23
+ end
24
+
25
+ describe 'accounts' do
26
+ use_vcr_cassette 'client/accounts'
27
+
28
+ it 'should retrieve all accounts' do
29
+ accounts = @client.accounts
30
+ accounts.length.should == 3
31
+ accounts.last.name.should == 'misc'
32
+ end
33
+ end
34
+
35
+ describe 'balance' do
36
+ context 'overall' do
37
+ use_vcr_cassette 'client/balance'
38
+
39
+ it 'should retreive the current balance' do
40
+ @client.balance.should == 12.34
41
+ end
42
+ end
43
+
44
+ context 'scoped to a particular account' do
45
+ use_vcr_cassette 'client/balance_account'
46
+
47
+ it 'should retrieve the account balance' do
48
+ @client.balance.should == 10.05
49
+ end
50
+ end
51
+ end
52
+
53
+ describe 'block_count' do
54
+ use_vcr_cassette 'client/block_count'
55
+
56
+ it 'should return the number of blocks in the longest block chain' do
57
+ @client.block_count.should == 130361
58
+ end
59
+ end
60
+
61
+ describe 'difficulty' do
62
+ use_vcr_cassette 'client/difficulty'
63
+
64
+ it 'should return the current difficulty' do
65
+ @client.difficulty.should == 567358.22457067
66
+ end
67
+ end
68
+
69
+ describe 'get_work' do
70
+ context 'when data is not supplied' do
71
+ use_vcr_cassette 'client/get_work'
72
+
73
+ it 'should return new formatted hash data to work on' do
74
+ work = @client.get_work
75
+ ['midstate', 'data', 'hash1', 'target'].each do |key|
76
+ work.has_key?(key).should be_true
77
+ end
78
+ end
79
+ end
80
+
81
+ context 'when data is supplied' do
82
+ it 'checks my answer'
83
+ it 'gives me more work'
84
+ end
85
+ end
86
+
87
+ describe 'info' do
88
+ use_vcr_cassette 'client/info'
89
+
90
+ it 'should return a hash containing bitcoind status information' do
91
+ @result = @client.info
92
+
93
+ @result['version'].should == 32100
94
+ @result['testnet'].should be_false
95
+
96
+ info_keys = ['version', 'balance', 'blocks', 'connections', 'proxy',
97
+ 'generate', 'genproclimit', 'difficulty', 'hashespersec', 'testnet',
98
+ 'keypoololdest', 'paytxfee', 'errors']
99
+ info_keys.each do |key|
100
+ @result.keys.include?(key).should be_true
101
+ end
102
+ end
103
+ end
104
+
105
+ describe 'new_address' do
106
+ use_vcr_cassette 'client/new_address', :record => :new_episodes
107
+
108
+ it 'should create a new address and return it' do
109
+ @client.new_address.should == '1EzxbYD4rFvZBjUEbtnKZ9KJdrqHB7mkZE'
110
+ end
111
+ end
112
+
113
+ describe 'transactions' do
114
+ context 'overall' do
115
+ use_vcr_cassette 'client/transactions'
116
+
117
+ it 'should retrieve all transactions' do
118
+ transactions = @client.transactions
119
+ transactions.length.should == 4
120
+ transactions.last.txid.should == 'txid4'
121
+ end
122
+ end
123
+
124
+ context 'scoped to a particular account' do
125
+ use_vcr_cassette 'client/transactions_account'
126
+
127
+ it 'should retrieve transactions for the account' do
128
+ transactions = @client.transactions('adent')
129
+ transactions.length.should == 1
130
+ transactions.last.txid.should == 'txid1'
131
+ end
132
+ end
133
+ end
134
+ end
@@ -0,0 +1,30 @@
1
+ ---
2
+ - !ruby/struct:VCR::HTTPInteraction
3
+ request: !ruby/struct:VCR::Request
4
+ method: :post
5
+ uri: http://testuser:testpass@localhost:8332/
6
+ body: ! '{"id":"jsonrpc","method":"getaccountaddress","params":["prefect"]}'
7
+ headers:
8
+ accept:
9
+ - ! '*/*; q=0.5, application/xml'
10
+ accept-encoding:
11
+ - gzip, deflate
12
+ content-length:
13
+ - '61'
14
+ response: !ruby/struct:VCR::Response
15
+ status: !ruby/struct:VCR::ResponseStatus
16
+ code: 200
17
+ message: OK
18
+ headers:
19
+ date:
20
+ - Sun, 12 Jun 2011 03:38:37 +0000
21
+ content-length:
22
+ - '76'
23
+ content-type:
24
+ - application/json
25
+ server:
26
+ - bitcoin-json-rpc/0.3.21-beta
27
+ body: ! '{"result":"1NqwGDRi9Gs4xm1BmPnGeMwgz1CowP6CeQ","error":null,"id":"jsonrpc"}
28
+
29
+ '
30
+ http_version: '1.1'
@@ -0,0 +1,30 @@
1
+ ---
2
+ - !ruby/struct:VCR::HTTPInteraction
3
+ request: !ruby/struct:VCR::Request
4
+ method: :post
5
+ uri: http://testuser:testpass@localhost:8332/
6
+ body: ! '{"id":"jsonrpc","method":"getbalance","params":["adent"]}'
7
+ headers:
8
+ accept:
9
+ - ! '*/*; q=0.5, application/xml'
10
+ accept-encoding:
11
+ - gzip, deflate
12
+ content-length:
13
+ - '54'
14
+ response: !ruby/struct:VCR::Response
15
+ status: !ruby/struct:VCR::ResponseStatus
16
+ code: 200
17
+ message: OK
18
+ headers:
19
+ date:
20
+ - Sun, 12 Jun 2011 03:36:43 +0000
21
+ content-length:
22
+ - '50'
23
+ content-type:
24
+ - application/json
25
+ server:
26
+ - bitcoin-json-rpc/0.3.21-beta
27
+ body: ! '{"result":0.02,"error":null,"id":"jsonrpc"}
28
+
29
+ '
30
+ http_version: '1.1'
@@ -0,0 +1,30 @@
1
+ ---
2
+ - !ruby/struct:VCR::HTTPInteraction
3
+ request: !ruby/struct:VCR::Request
4
+ method: :post
5
+ uri: http://testuser:testpass@localhost:8332/
6
+ body: ! '{"id":"jsonrpc","method":"getnewaddress","params":["adent"]}'
7
+ headers:
8
+ accept:
9
+ - ! '*/*; q=0.5, application/xml'
10
+ accept-encoding:
11
+ - gzip, deflate
12
+ content-length:
13
+ - '60'
14
+ response: !ruby/struct:VCR::Response
15
+ status: !ruby/struct:VCR::ResponseStatus
16
+ code: 200
17
+ message: OK
18
+ headers:
19
+ date:
20
+ - Mon, 13 Jun 2011 04:40:14 +0000
21
+ content-length:
22
+ - '76'
23
+ content-type:
24
+ - application/json
25
+ server:
26
+ - bitcoin-json-rpc/0.3.21-beta
27
+ body: ! '{"result":"15GsE7o3isyQ7ygzh8Cya58oetrGYygdoi","error":null,"id":"jsonrpc"}
28
+
29
+ '
30
+ http_version: '1.1'
@@ -0,0 +1,60 @@
1
+ ---
2
+ - !ruby/struct:VCR::HTTPInteraction
3
+ request: !ruby/struct:VCR::Request
4
+ method: :post
5
+ uri: http://testuser:testpass@localhost:8332/
6
+ body: ! '{"id":"jsonrpc","method":"sendfrom","params":["adent","destinationaddress",0.1]}'
7
+ headers:
8
+ accept:
9
+ - ! '*/*; q=0.5, application/xml'
10
+ accept-encoding:
11
+ - gzip, deflate
12
+ content-length:
13
+ - '107'
14
+ response: !ruby/struct:VCR::Response
15
+ status: !ruby/struct:VCR::ResponseStatus
16
+ code: 200
17
+ message: OK
18
+ headers:
19
+ date:
20
+ - Sun, 12 Jun 2011 03:59:59 +0000
21
+ content-length:
22
+ - '106'
23
+ content-type:
24
+ - application/json
25
+ server:
26
+ - bitcoin-json-rpc/0.3.21-beta
27
+ body: ! '{"result":"newtxid","error":null,"id":"jsonrpc"}
28
+
29
+ '
30
+ http_version: '1.1'
31
+
32
+ - !ruby/struct:VCR::HTTPInteraction
33
+ request: !ruby/struct:VCR::Request
34
+ method: :post
35
+ uri: http://testuser:testpass@localhost:8332/
36
+ body: ! '{"id":"jsonrpc","method":"gettransaction","params":["newtxid"]}'
37
+ headers:
38
+ accept:
39
+ - ! '*/*; q=0.5, application/xml'
40
+ accept-encoding:
41
+ - gzip, deflate
42
+ content-length:
43
+ - '120'
44
+ response: !ruby/struct:VCR::Response
45
+ status: !ruby/struct:VCR::ResponseStatus
46
+ code: 200
47
+ message: OK
48
+ headers:
49
+ date:
50
+ - Mon, 13 Jun 2011 04:00:01 +0000
51
+ content-length:
52
+ - '289'
53
+ content-type:
54
+ - application/json
55
+ server:
56
+ - bitcoin-json-rpc/0.3.21-beta
57
+ body: ! '{"result":{"amount":0.01,"confirmations":1,"txid":"newtxid","time":1307851201,"details":[{"account":"adent","address":"addr1","category":"send","amount":0.1}]},"error":null,"id":"jsonrpc"}
58
+
59
+ '
60
+ http_version: '1.1'