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