cryptsy 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.
- checksums.yaml +7 -0
- data/LICENSE +19 -0
- data/README.md +166 -0
- data/lib/cryptsy.rb +7 -0
- data/lib/cryptsy/client.rb +171 -0
- data/lib/cryptsy/confirmation_poller.rb +47 -0
- data/lib/cryptsy/errors.rb +3 -0
- data/lib/cryptsy/version.rb +3 -0
- data/lib/cryptsy/web_client.rb +134 -0
- data/spec/client_spec.rb +71 -0
- data/spec/spec_helper.rb +1 -0
- data/spec/web_client_spec.rb +33 -0
- metadata +86 -0
checksums.yaml
ADDED
@@ -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.
|
data/README.md
ADDED
@@ -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
|
data/lib/cryptsy.rb
ADDED
@@ -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,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
|
data/spec/client_spec.rb
ADDED
@@ -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
|
data/spec/spec_helper.rb
ADDED
@@ -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:
|