cryptsy 0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 86fbb550ff9903ff92f1ed6bd6f72addb0f7c262
4
+ data.tar.gz: 21735c159cd27ce56adcbf629264b5da170692bd
5
+ SHA512:
6
+ metadata.gz: f541a58e0610ba764d25de2cc0d2c4173b33f77aa58d96074e16a70113393c32c6d0a9b43ca58b5c0ad251f1b6c159d27031c1e2187166c8c362ff3d51ebe534
7
+ data.tar.gz: 7dbafded98719d639791e1997bd4199e9af63382967ff02ce939a3c513423085cd5a954d0a096b53b0668b20234750572f4b5d699647edd4bb42526423fa740c
data/LICENSE ADDED
@@ -0,0 +1,19 @@
1
+ Copyright (c) 2014 Ian Unruh
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ of this software and associated documentation files (the "Software"), to deal
5
+ in the Software without restriction, including without limitation the rights
6
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ copies of the Software, and to permit persons to whom the Software is
8
+ furnished to do so, subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in
11
+ all copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ THE SOFTWARE.
@@ -0,0 +1,166 @@
1
+ # Cryptsy
2
+
3
+ Provides an idiomatic Ruby client for the authenticated Cryptsy API
4
+
5
+ ## JSON client
6
+
7
+ Grab your API keys from the Crypsty website
8
+
9
+ ```ruby
10
+ client = Cryptsy::Client.new('YOUR PUBLIC KEY', 'YOUR PRIVATE KEY')
11
+ ```
12
+
13
+ ### Check your account details
14
+
15
+ ```ruby
16
+ # Get account blances
17
+ client.info.balances_available
18
+
19
+ # Get transfers, transactions, open orders
20
+ client.transfers
21
+ client.transactions
22
+ client.orders
23
+ ```
24
+
25
+ ### Query markets
26
+
27
+ ```ruby
28
+ client.markets
29
+ client.market_by_pair('DOGE', 'BTC')
30
+ ```
31
+
32
+ ### Generate new receive addresses
33
+
34
+ ```ruby
35
+ client.generate_deposit_address('DOGE') # => 'DTP2Na7P4JpwhUuPTdJWjGzAK9P5VXF5Zd'
36
+ client.generate_deposit_address('BTC') # => '156q4WMvWmCSmTdZSVVn8zdnDFJWZsb6XW'
37
+ ```
38
+
39
+ ### Make withdrawals
40
+
41
+ ```ruby
42
+ client.make_withdrawal('DKkNNCF2DRcHtSgbKeDTVd2T8qjng1Z8hV', 1250.0)
43
+ ```
44
+
45
+ Note that this method only works for addresses that have been pre-approved. Use the web client
46
+ if you wish to automate the pre-approval process.
47
+
48
+ ## Web client
49
+
50
+ This package provides a web client for doing unsafe operations on Cryptsy. The official JSON
51
+ API does not allow you to withdraw funds to addresses that have not been pre-approved. Nor does
52
+ it let you pre-approval addresses. Therefore, you would be forced to login to the HTML web interface,
53
+ and input your password and TFA token to do withdrawals and add trusted addresses.
54
+
55
+ This client is rough around the edges, but the basic functionality is there. It has many extra dependencies,
56
+ so you have to explicitly require it and install the dependencies.
57
+
58
+ ```sh
59
+ gem install faraday-cookie_jar nokogiri rotp
60
+ ```
61
+
62
+ ```ruby
63
+ require 'cryptsy/web_client'
64
+
65
+ web_client = Cryptsy::WebClient.new('YOUR CRYPTSY USERNAME', 'YOUR CRYPTSY PASSWORD', 'YOUR TFA SECRET')
66
+ web_client.login
67
+ web_client.pincode
68
+ ```
69
+
70
+ Now that you have a session on Cryptsy, you can perform privileged operations.
71
+
72
+ ### Withdrawals
73
+
74
+ If you wish to make a withdrawal to an address that has not been pre-approved, use the following:
75
+
76
+ ```ruby
77
+ web_client.make_withdrawal(94, 'DKkNNCF2DRcHtSgbKeDTVd2T8qjng1Z8hV', 1250.0)
78
+ ```
79
+
80
+ You will receive a confirmation email after a short period. The link in this email must be visited
81
+ for the withdrawal to continue.
82
+
83
+ Note that you **will not** be able to use this for trusted addresses. Instead, use the respective method
84
+ on the regular JSON client.
85
+
86
+ ### Trusted addresses
87
+
88
+ If you wish to make an address trusted, use the following:
89
+
90
+ ```ruby
91
+ web_client.add_trusted_address('DQRhettwhyR6xeK6xFQ2nbhjhSTgZzdgMR')
92
+ ```
93
+
94
+ You will receive a confirmatin email after a short period. The link in this email must be visited
95
+ for the address to become trusted.
96
+
97
+ Note that you **will not** be able to use the web client to make withdrawals to this address now. Instead,
98
+ use the respective method on the regular JSON client.
99
+
100
+ ### Caching sessions
101
+
102
+ The web client uses Faraday with middleware for [HTTP::CookieJar](https://github.com/sparklemotion/http-cookie).
103
+
104
+ The cookie jar is accessible, so you can save and load cookies to a file between uses.
105
+
106
+ ```ruby
107
+ jar = web_client.cookie_jar
108
+
109
+ jar.load('path/to/cookies.txt')
110
+ jar.cleanup
111
+
112
+ jar.save('path/to/cookies.txt', session: true)
113
+ ```
114
+
115
+ ## Confirmation email polling
116
+
117
+ Take automation to the next level! Using `ConfirmationPoller`, you can scan the email account associated with your
118
+ Cryptsy account. Combining this with the web client allows you to automatically confirm:
119
+
120
+ - Trusted addresses
121
+ - Withdrawals to untrusted addresses
122
+
123
+ Refer to `examples/gmail_poller.rb` to see basic integration with Gmail. Combine it with the web client like so:
124
+
125
+ ```ruby
126
+ adapter = GmailAdapter.new('GMAIL USERNAME', 'GMAIL PASSWORD')
127
+ web_client = Cryptsy::WebClient.new('CRYPTSY USERNAME', 'CRYPTSY PASSWORD', 'TFA SECRET')
128
+ poller = Cryptsy::ConfirmationPoller.new(adapter, CONFIRM_TRUSTED_ADDRESS_PATTERN)
129
+
130
+ poller.run_until_found.each do |link|
131
+ web_client.get(link)
132
+ end
133
+
134
+ adapter.logout
135
+ ```
136
+
137
+ It's recommended to setup an application-specific password instead of using your primary Gmail password.
138
+
139
+ ## Security concerns
140
+
141
+ ### SSL verification
142
+
143
+ The certificate for `https://api.cryptsy.com` is invalid. Therefore, any clients that connect to it
144
+ must disable SSL verification. **This opens up the possibility for a MITM attack.**
145
+
146
+ Until this is fixed, avoid experimenting with the JSON client on untrusted networks until this
147
+ is corrected. The web client does not have this vulnerability, the `https://www.cryptsy.com` certificate
148
+ is correct.
149
+
150
+ ### Plaintext credentials
151
+
152
+ Using both clients will result in a large number of credentials needing to be stored in plaintext.
153
+
154
+ This includes the following:
155
+
156
+ - Cryptsy username & password
157
+ - Cryptsy API key pair
158
+ - Cryptsy two-factor authentication (TFA) secret
159
+
160
+ Therefore, you should isolate the use of this client away from a public-facing service. On a separate VM,
161
+ you can use a background worker process, like Sidekiq or Resque.
162
+
163
+ ## To-do
164
+
165
+ - [FIX] Add error handling to web client
166
+ - [FEAT] Add auto-confirm for emailed confirmation links
@@ -0,0 +1,7 @@
1
+ require 'faraday'
2
+
3
+ require 'cryptsy/version'
4
+
5
+ require 'cryptsy/client'
6
+ require 'cryptsy/confirmation_poller'
7
+ require 'cryptsy/errors'
@@ -0,0 +1,171 @@
1
+ require 'hashie'
2
+ require 'json'
3
+ require 'openssl'
4
+ require 'uri'
5
+
6
+ module Cryptsy
7
+ class Client
8
+ DEFAULT_OPTIONS = {
9
+ url: 'https://api.cryptsy.com',
10
+ ssl: {
11
+ verify: false
12
+ }
13
+ }
14
+
15
+ ORDER_TYPE_BUY = 'Buy'
16
+ ORDER_TYPE_SELL = 'Sell'
17
+
18
+ # @param [String] public_key
19
+ # @param [String] private_key
20
+ # @param [Hash] options
21
+ def initialize(public_key, private_key, options = {})
22
+ @public_key = public_key
23
+ @private_key = private_key
24
+ @digest = OpenSSL::Digest::SHA512.new
25
+ @connection = Faraday.new(DEFAULT_OPTIONS.merge(options))
26
+ end
27
+
28
+ def info
29
+ call(:getinfo)
30
+ end
31
+
32
+ def markets
33
+ call(:getmarkets)
34
+ end
35
+
36
+ def market_by_pair(primary_code, secondary_code)
37
+ markets.find do |market|
38
+ market.primary_currency_code == normalize_currency_code(primary_code) &&
39
+ market.secondary_currency_code == normalize_currency_code(secondary_code)
40
+ end
41
+ end
42
+
43
+ def orders(market_id)
44
+ call(:myorders, marketid: market_id)
45
+ end
46
+
47
+ def all_orders
48
+ call(:allmyorders)
49
+ end
50
+
51
+ def trades(market_id, limit = 200)
52
+ call(:mytrades, marketid: market_id, limit: limit)
53
+ end
54
+
55
+ def all_trades
56
+ call(:allmytrades)
57
+ end
58
+
59
+ def transactions
60
+ call(:mytransactions)
61
+ end
62
+
63
+ def transfers
64
+ call(:mytransfers)
65
+ end
66
+
67
+ def market_depth(market_id)
68
+ call(:depth, marketid: market_id)
69
+ end
70
+
71
+ def market_orders(market_id)
72
+ call(:marketorders, marketid: market_id)
73
+ end
74
+
75
+ def market_trades(market_id)
76
+ call(:markettrades, marketid: market_id)
77
+ end
78
+
79
+ def create_buy_order(market_id, quantity, price)
80
+ create_order(market_id, ORDER_TYPE_BUY, quantity, price)
81
+ end
82
+
83
+ def create_sell_order(market_id, quantity, price)
84
+ create_order(market_id, ORDER_TYPE_SELL, quantity, price)
85
+ end
86
+
87
+ def create_order(market_id, order_type, quantity, price)
88
+ call(:createorder, marketid: market_id, ordertype: order_type, quantity: quantity, price: price)
89
+ end
90
+
91
+ def cancel_order(order_id)
92
+ call(:cancelorder, orderid: order_id)
93
+ end
94
+
95
+ def cancel_orders(market_id)
96
+ call(:cancelmarketorders, marketid: market_id)
97
+ end
98
+
99
+ def cancel_all_orders
100
+ call(:cancelallorders)
101
+ end
102
+
103
+ def calculate_buy_fees(quantity, price)
104
+ calculate_fees(ORDER_TYPE_BUY, quantity, price)
105
+ end
106
+
107
+ def calculate_sell_fees(quantity, price)
108
+ calculate_fees(ORDER_TYPE_SELL, quantity, price)
109
+ end
110
+
111
+ def calculate_fees(order_type, quantity, price)
112
+ call(:calculatefees, ordertype: order_type, quantity: quantity, price: price)
113
+ end
114
+
115
+ def generate_new_address(currency)
116
+ if currency.is_a?(Integer)
117
+ call(:generatenewaddress, currencyid: currency).address
118
+ else
119
+ call(:generatenewaddress, currencycode: normalize_currency_code(currency)).address
120
+ end
121
+ end
122
+
123
+ def make_withdrawal(address, amount)
124
+ call(:makewithdrawal, address: address, amount: amount)
125
+ end
126
+
127
+ # @raise [CryptsyError]
128
+ # @param [Symbol] method_name
129
+ # @param [Hash] params
130
+ # @return [Object]
131
+ def call(method_name, params = {})
132
+ request = {
133
+ method: method_name,
134
+ nonce: Time.now.to_i
135
+ }.merge(params)
136
+
137
+ body = URI.encode_www_form(request)
138
+ signature = OpenSSL::HMAC.hexdigest(@digest, @private_key, body)
139
+
140
+ response = @connection.post do |req|
141
+ req.url 'api'
142
+ req.body = body
143
+
144
+ req.headers['Content-Type'] = 'application/x-www-form-urlencoded'
145
+ req.headers['Key'] = @public_key
146
+ req.headers['Sign'] = signature
147
+ end
148
+
149
+ process_response(response)
150
+ end
151
+
152
+ # @raise [CryptsyError]
153
+ # @param [Faraday::Response] response
154
+ # @return [Object]
155
+ def process_response(response)
156
+ body = Hashie::Mash.new(JSON.parse(response.body))
157
+
158
+ unless body.success.to_i == 1
159
+ raise ClientError, body.error
160
+ end
161
+
162
+ body.return
163
+ end
164
+
165
+ # @param [Object] code
166
+ # @return [String]
167
+ def normalize_currency_code(code)
168
+ code.to_s.upcase
169
+ end
170
+ end
171
+ end
@@ -0,0 +1,47 @@
1
+ require 'nokogiri'
2
+
3
+ module Cryptsy
4
+ class ConfirmationPoller
5
+ # @param [Object] adapter
6
+ # @param [Regexp] pattern
7
+ def initialize(adapter, pattern)
8
+ @adapter = adapter
9
+ @pattern = pattern
10
+ end
11
+
12
+ # @return [Enumerable]
13
+ def run_once
14
+ links = []
15
+
16
+ @adapter.call do |email|
17
+ scan_links(links, email)
18
+ end
19
+
20
+ links
21
+ end
22
+
23
+ # @param [Integer] sleep_interval
24
+ # @return [void]
25
+ def run_until_found(sleep_interval = 3)
26
+ loop do
27
+ links = run_once
28
+ return links unless links.empty?
29
+ sleep sleep_interval
30
+ end
31
+ end
32
+
33
+ private
34
+
35
+ # @param [Array] links
36
+ # @param [String] email
37
+ # @return [void]
38
+ def scan_links(links, email)
39
+ doc = Nokogiri::HTML(email.to_s)
40
+ doc.xpath('//a').each do |link|
41
+ if link[:href] =~ @pattern
42
+ links.push($1)
43
+ end
44
+ end
45
+ end
46
+ end # ConfirmationPoller
47
+ end
@@ -0,0 +1,3 @@
1
+ module Cryptsy
2
+ ClientError = Class.new(RuntimeError)
3
+ end
@@ -0,0 +1,3 @@
1
+ module Cryptsy
2
+ VERSION = '0.1'
3
+ end
@@ -0,0 +1,134 @@
1
+ require 'faraday-cookie_jar'
2
+ require 'nokogiri'
3
+ require 'rotp'
4
+
5
+ module Cryptsy
6
+ # Unsafe client that allows you to do things that the Cryptsy API does not permit,
7
+ # including withdrawals to untrusted addresses, as well as pre-approving addresses
8
+ # for withdrawals
9
+ class WebClient
10
+ DEFAULT_OPTIONS = {
11
+ url: 'https://www.cryptsy.com'
12
+ }
13
+
14
+ # @param [HTTP::CookieJar]
15
+ attr_reader :cookie_jar
16
+
17
+ # @param [String] username
18
+ # @param [String] password
19
+ # @param [String] tfa_secret
20
+ # @param [Hash] options
21
+ def initialize(username, password, tfa_secret, options = {})
22
+ @username = username
23
+ @password = password
24
+ @tfa = ROTP::TOTP.new(tfa_secret)
25
+
26
+ @cookie_jar = HTTP::CookieJar.new
27
+
28
+ @connection = Faraday.new(DEFAULT_OPTIONS.merge(options)) do |builder|
29
+ builder.use :cookie_jar, jar: @cookie_jar
30
+ builder.request :url_encoded
31
+ builder.adapter Faraday.default_adapter
32
+ end
33
+ end
34
+
35
+ # Performs login operation using the configured username and password
36
+ # @return [Faraday::Response]
37
+ def login
38
+ request = {
39
+ 'data[User][username]' => @username,
40
+ 'data[User][password]' => @password,
41
+ }
42
+
43
+ post_with_csrf('/users/login', request)
44
+ end
45
+
46
+ # Finishes login operation using TOTP and TFA secret
47
+ # @return [Faraday::Response]
48
+ def pincode
49
+ request = {
50
+ 'data[User][pincode]' => @tfa.now
51
+ }
52
+
53
+ post_with_csrf('/users/pincode', request)
54
+ end
55
+
56
+ # Submits a trusted address for pre-approved withdrawals
57
+ #
58
+ # @param [Integer] currency_id
59
+ # @param [String] address
60
+ # @return [Faraday::Response]
61
+ def add_trusted_address(currency_id, address)
62
+ request = {
63
+ 'data[Withdrawal][currency_id]' => currency_id,
64
+ 'data[Withdrawal][address]' => address,
65
+ 'data[Withdrawal][existing_password]' => @password,
66
+ 'data[Withdrawal][pincode]' => @tfa.now,
67
+ }
68
+
69
+ post_with_csrf('/users/addtrustedaddress', request)
70
+ end
71
+
72
+ # Submits a request for withdrawal to an untrusted address
73
+ #
74
+ # @param [Integer] currency_id
75
+ # @param [String] address
76
+ # @param [Float] amount
77
+ # @return [Faraday::Response]
78
+ def make_withdrawal(currency_id, address, amount)
79
+ request = {
80
+ 'data[Withdrawal][fee]' => '1.00000000',
81
+ 'data[Withdrawal][wdamount]' => amount,
82
+ 'data[Withdrawal][address]' => address,
83
+ 'data[Withdrawal][approvedaddress]' => '',
84
+ 'data[Withdrawal][existing_password]' => @password,
85
+ 'data[Withdrawal][pincode]' => @tfa.now,
86
+ }
87
+
88
+ post_with_csrf("/users/makewithdrawal/#{currency_id}", request)
89
+ end
90
+
91
+ # @param [String] url
92
+ # @return [Faraday::Response]
93
+ def get(url)
94
+ @connection.get(url)
95
+ end
96
+
97
+ # @param [String] url
98
+ # @param [Hash] body
99
+ # @return [Faraday::Response]
100
+ def post(url, body)
101
+ @connection.post(url, body)
102
+ end
103
+
104
+ # Performs an initial GET request to the given URL to obtain any CSRF tokens,
105
+ # injects them into the given request, then performs a POST request to the given URL
106
+ #
107
+ # @param [String] url
108
+ # @param [Hash] request
109
+ # @return [Faraday::Request]
110
+ def post_with_csrf(url, request)
111
+ prepare_request(get(url), request)
112
+ post(url, request)
113
+ end
114
+
115
+ private
116
+
117
+ # @param [Faraday::Response] initial_response
118
+ # @param [Hash] new_request
119
+ # @return [void]
120
+ def prepare_request(initial_response, new_request)
121
+ doc = Nokogiri::HTML(initial_response.body)
122
+
123
+ # Inject CSRF token into new request
124
+ doc.xpath('//input').each do |input|
125
+ if input[:name] =~ /_Token/
126
+ new_request[input[:name]] = input[:value]
127
+ end
128
+ end
129
+
130
+ # Set the request method
131
+ new_request['_method'] = 'POST'
132
+ end
133
+ end
134
+ end
@@ -0,0 +1,71 @@
1
+ require 'spec_helper'
2
+
3
+ describe Cryptsy::Client do
4
+ before(:all) do
5
+ @public_key = ENV['CRYPTSY_PUBKEY']
6
+ @private_key = ENV['CRYPTSY_PRIVKEY']
7
+
8
+ unless @public_key && @private_key
9
+ fail 'API keys are required to run this spec'
10
+ end
11
+ end
12
+
13
+ subject { Cryptsy::Client.new(@public_key, @private_key) }
14
+
15
+ describe '#info' do
16
+ it 'displays current user info' do
17
+ result = subject.info
18
+ expect(result.balances_available).to be_a(Hash)
19
+ expect(result.servertimestamp).to be_an(Integer)
20
+ end
21
+ end
22
+
23
+ describe '#calculate_fees' do
24
+ it 'calculates the fee for a buy order' do
25
+ quantity = 1000
26
+ price = 10
27
+
28
+ result = subject.calculate_buy_fees(quantity, price)
29
+
30
+ expect(result.net.to_f).to eql((quantity * price) + result.fee.to_f)
31
+ end
32
+
33
+ it 'calculates the fee for a sell order' do
34
+ quantity = 1000
35
+ price = 10
36
+
37
+ result = subject.calculate_sell_fees(quantity, price)
38
+
39
+ expect(result.net.to_f).to eql((quantity * price) - result.fee.to_f)
40
+ end
41
+ end
42
+
43
+ describe '#generate_new_address' do
44
+ it 'raises an error when using an invalid currency' do
45
+ expect {
46
+ subject.generate_new_address('HERP')
47
+ }.to raise_error(Cryptsy::ClientError)
48
+ end
49
+
50
+ it 'returns a newly generated receive address' do
51
+ result = subject.generate_new_address('DOGE')
52
+ expect(result.size).to eql(34)
53
+ end
54
+ end
55
+
56
+ describe '#transactions' do
57
+ it 'returns all deposits and withdrawals' do
58
+ expect(subject.transactions).to be_an(Enumerable)
59
+ end
60
+ end
61
+
62
+ describe '#market_by_pair' do
63
+ it 'returns market by its currency code pair' do
64
+ expect(subject.market_by_pair('DOGE', 'BTC')).to be_a(Hash)
65
+ end
66
+
67
+ it 'returns nil for non-existant markets' do
68
+ expect(subject.market_by_pair('DOGE', 'HERP')).to be_nil
69
+ end
70
+ end
71
+ end
@@ -0,0 +1 @@
1
+ require 'cryptsy'
@@ -0,0 +1,33 @@
1
+ require 'spec_helper'
2
+ require 'cryptsy/web_client'
3
+
4
+ describe Cryptsy::WebClient do
5
+ before(:all) do
6
+ @username = ENV['CRYPTSY_USERNAME']
7
+ @password = ENV['CRYPTSY_PASSWORD']
8
+ @tfa_secret = ENV['CRYPTSY_TFA_SECRET']
9
+
10
+ unless @username && @password && @tfa_secret
11
+ fail 'Cryptsy login details are required to run this spec'
12
+ end
13
+ end
14
+
15
+ subject { Cryptsy::WebClient.new(@username, @password, @tfa_secret) }
16
+
17
+ describe '#login' do
18
+ it 'logs into Cryptsy' do
19
+ response = subject.login
20
+ # Should respond with 302
21
+ # Location header should be /users/dashboard or /users/pincode
22
+ end
23
+
24
+ context 'with bad credentials' do
25
+ subject { Cryptsy::WebClient.new('herp', 'derp', 'lolz') }
26
+ it 'fails to login' do
27
+ response = subject.login
28
+ # Should respond with 200
29
+ # Look for div#flashMessages
30
+ end
31
+ end
32
+ end
33
+ end
metadata ADDED
@@ -0,0 +1,86 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: cryptsy
3
+ version: !ruby/object:Gem::Version
4
+ version: '0.1'
5
+ platform: ruby
6
+ authors:
7
+ - Ian Unruh
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-04-01 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: faraday
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: hashie
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ description: API client for interacting with Cryptsy
42
+ email: ianunruh@gmail.com
43
+ executables: []
44
+ extensions: []
45
+ extra_rdoc_files: []
46
+ files:
47
+ - LICENSE
48
+ - README.md
49
+ - lib/cryptsy.rb
50
+ - lib/cryptsy/client.rb
51
+ - lib/cryptsy/confirmation_poller.rb
52
+ - lib/cryptsy/errors.rb
53
+ - lib/cryptsy/version.rb
54
+ - lib/cryptsy/web_client.rb
55
+ - spec/client_spec.rb
56
+ - spec/spec_helper.rb
57
+ - spec/web_client_spec.rb
58
+ homepage: https://github.com/ianunruh/cryptsy
59
+ licenses:
60
+ - MIT
61
+ metadata: {}
62
+ post_install_message:
63
+ rdoc_options: []
64
+ require_paths:
65
+ - lib
66
+ required_ruby_version: !ruby/object:Gem::Requirement
67
+ requirements:
68
+ - - ">="
69
+ - !ruby/object:Gem::Version
70
+ version: '0'
71
+ required_rubygems_version: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ requirements: []
77
+ rubyforge_project:
78
+ rubygems_version: 2.2.2
79
+ signing_key:
80
+ specification_version: 4
81
+ summary: API client for interacting with Cryptsy
82
+ test_files:
83
+ - spec/client_spec.rb
84
+ - spec/spec_helper.rb
85
+ - spec/web_client_spec.rb
86
+ has_rdoc: