cryptomnio 0.2.2
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 +21 -0
- data/lib/cryptomnio/version.rb +4 -0
- data/lib/cryptomnio.rb +839 -0
- metadata +56 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 9ad53c1623ebabab3c246cd9a2e4a2a60330f5d441b0664a79cb31869a8f1db7
|
|
4
|
+
data.tar.gz: 5d1681703eeff7980f07160f33d4fc9c9909f0b0aa7326e840f28499412a5b23
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 2d3d4de13ba6db5fa209aeacb7a28742166ca36658868b3af95817dd1f802da0f71039f5fca5e691847479188bdc7bcba12b1a853326416296964053dd099dae
|
|
7
|
+
data.tar.gz: 715b52c02a5c359e58ba91f64d27b9584af5c28c780f9eee8e57137320f25cd4919e57119ad5acd69a081778d2278736eab2e185d0f36c46402c605484bd0d9e
|
data/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Dustin D. Trammell
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
data/lib/cryptomnio.rb
ADDED
|
@@ -0,0 +1,839 @@
|
|
|
1
|
+
require 'rest-client'
|
|
2
|
+
require 'openssl'
|
|
3
|
+
require 'base64'
|
|
4
|
+
require 'json'
|
|
5
|
+
require_relative "cryptomnio/version"
|
|
6
|
+
|
|
7
|
+
##
|
|
8
|
+
# This is the root Cryptomnio object Class. It contains global constants.
|
|
9
|
+
|
|
10
|
+
class Cryptomnio
|
|
11
|
+
attr_reader :VERSION
|
|
12
|
+
attr_reader :API_VERSION
|
|
13
|
+
|
|
14
|
+
# Creates a new Cryptomnio object
|
|
15
|
+
def initialize
|
|
16
|
+
##
|
|
17
|
+
# Class Instance Variables
|
|
18
|
+
|
|
19
|
+
# The name of the Gem
|
|
20
|
+
@NAME = "Cryptomnio Ruby Gem"
|
|
21
|
+
# The name of the Gem's Author
|
|
22
|
+
@AUTHOR = "Dustin D. Trammell"
|
|
23
|
+
# The publication date of the current Gem version (from lib/cryptomnio/version.rb)
|
|
24
|
+
@DATE = DATE
|
|
25
|
+
# The Gem version (from lib/cryptomnio/version.rb)
|
|
26
|
+
@VERSION = VERSION
|
|
27
|
+
# The Cryptomnio API Version
|
|
28
|
+
@API_VERSION = "0.24.0"
|
|
29
|
+
# API URI Path Version Slug @URI_VERSION = "/v1"
|
|
30
|
+
@URI_VERSION = ""
|
|
31
|
+
|
|
32
|
+
puts "Cryptomnio Initalized" if $DEBUG
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# Returns the Gem information
|
|
36
|
+
def geminfo
|
|
37
|
+
info = @NAME + " " + @VERSION + "\n"
|
|
38
|
+
info << @DATE + " - " + @AUTHOR + "\n"
|
|
39
|
+
info << "Cryptomnio API Version: " + @API_VERSION + "\n"
|
|
40
|
+
|
|
41
|
+
return info
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# HTTP Request Logging module for HTTPClient decoration
|
|
46
|
+
module LogHTTPRequest
|
|
47
|
+
# Gets the specified URL
|
|
48
|
+
def get(url)
|
|
49
|
+
puts "Sending Request for #{url}"
|
|
50
|
+
super
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# HTTPClient Class Wrapper
|
|
55
|
+
class HTTPClient
|
|
56
|
+
def initialize(client = RestClient)
|
|
57
|
+
@client = client
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def get(*args)
|
|
61
|
+
@client.get(*args)
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
##
|
|
66
|
+
# This is the root Cryptomnio REST object Class.
|
|
67
|
+
#
|
|
68
|
+
# It inherets from the Cryptomnio object Class and contains REST-specific methods and variables.
|
|
69
|
+
|
|
70
|
+
class Cryptomnio::REST < Cryptomnio
|
|
71
|
+
|
|
72
|
+
##
|
|
73
|
+
# Creates a new Cryptomnio::REST object.
|
|
74
|
+
#
|
|
75
|
+
# If "http_proxy" is set in the program's environment, it will be used for
|
|
76
|
+
# the RestClient's proxy setting.
|
|
77
|
+
|
|
78
|
+
def initialize
|
|
79
|
+
# Call Superclass initialize
|
|
80
|
+
super
|
|
81
|
+
|
|
82
|
+
# Get the HTTP proxy from the environment (if there is one)
|
|
83
|
+
RestClient.proxy = ENV['http_proxy'] if ENV['http_proxy']
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
##
|
|
88
|
+
# This is the Cryptomnio REST Client object Class
|
|
89
|
+
#
|
|
90
|
+
# It inherets from the Cryptomnio::REST object Class and contains REST Client-specific methods and variables.
|
|
91
|
+
|
|
92
|
+
class Cryptomnio::REST::Client < Cryptomnio::REST
|
|
93
|
+
attr_accessor :config
|
|
94
|
+
|
|
95
|
+
# Switch Object's Context (Account + Venue to operate as)
|
|
96
|
+
def context_switch( label )
|
|
97
|
+
puts "Switching to context \"%s\"" % label if $DEBUG
|
|
98
|
+
|
|
99
|
+
# Validate that a context for label exists
|
|
100
|
+
raise "No context found for label: %s" % label if ! @config[:contexts][label]
|
|
101
|
+
puts @config[:contexts][label].inspect if $DEBUG
|
|
102
|
+
|
|
103
|
+
# Validate that the required parameters exist
|
|
104
|
+
raise "Missing Configuration Parameter: contexts[:venue]" if ! @config[:contexts][label][:venue]
|
|
105
|
+
raise "Missing Configuration Parameter: contexts[:accountid]" if ! @config[:contexts][label][:accountid]
|
|
106
|
+
raise "Missing Configuration Parameter: contexts[:venuekeyid]" if ! @config[:contexts][label][:venuekeyid]
|
|
107
|
+
|
|
108
|
+
# Switch to the context
|
|
109
|
+
@context = @config[:contexts][label]
|
|
110
|
+
|
|
111
|
+
# Return true on success
|
|
112
|
+
puts "Context Switch Successful!" if $DEBUG
|
|
113
|
+
return true
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
# Cryptomnio Key Authentication
|
|
117
|
+
def auth_cryptomnio( method, uripath )
|
|
118
|
+
puts "Building Authentication Credentials" if $DEBUG
|
|
119
|
+
|
|
120
|
+
# Create concatenated method and base URL path string
|
|
121
|
+
methodpath = method.to_s.upcase + uripath # convert RestClient's method symbol to uppercase string
|
|
122
|
+
puts " Method+Path = %s" % methodpath if $DEBUG
|
|
123
|
+
|
|
124
|
+
# Create HMAC SHA-512 authentication hash of methodpath using secret key
|
|
125
|
+
@config[:auth_string] = Base64.encode64(OpenSSL::HMAC.digest('sha512', @config[:secret_key], methodpath)).split.join # .split.join is to remove '/n' inserted into signature by HMAC
|
|
126
|
+
|
|
127
|
+
# Output
|
|
128
|
+
if $DEBUG && $VERBOSE
|
|
129
|
+
puts " Access-Key: %s" % @config[:access_key]
|
|
130
|
+
puts " Signature: %s" % @config[:auth_string]
|
|
131
|
+
puts
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
# Return the signature
|
|
135
|
+
return @config[:auth_string]
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
# Test method wrapper for Cryptomnio Key Authentication
|
|
139
|
+
def auth_cryptomnio_test
|
|
140
|
+
# TODO: Find better CMA test call to use
|
|
141
|
+
method = :get
|
|
142
|
+
endpoint = "/venues"
|
|
143
|
+
#endpoint = "/nope"
|
|
144
|
+
uripath = @URI_VERSION + endpoint
|
|
145
|
+
signature = self.auth_cryptomnio( method, uripath )
|
|
146
|
+
|
|
147
|
+
puts "Testing Cryptomnio Key Authentication" if $VERBOSE
|
|
148
|
+
if $DEBUG
|
|
149
|
+
puts " Requesting: %s %s%s" % [ method, @config[:apiurl], uripath ]
|
|
150
|
+
puts " Access-Key: %s" % @config[:access_key]
|
|
151
|
+
puts " Signature: %s" % signature
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
# Test Authentication Call
|
|
155
|
+
self._rest_call( method, api = :core, uripath = uripath, uriparameters = nil, error_string = "Cryptomnio Key Authentication test failed." )
|
|
156
|
+
|
|
157
|
+
# Success
|
|
158
|
+
return true
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
# Generic REST API call method
|
|
162
|
+
# Provides RestClient call with needed authentication headers and error handling
|
|
163
|
+
def _rest_call( method = :get, api = :core, uripath = "/", uriparameters = nil, error_string = "REST API Call Failed", body = nil )
|
|
164
|
+
# Get the HMAC SHA-512 hash signature value for the method+URIpath
|
|
165
|
+
signature = self.auth_cryptomnio( method, @URI_VERSION + uripath )
|
|
166
|
+
|
|
167
|
+
# Build the URI from the URIpath and optional parameters
|
|
168
|
+
case api
|
|
169
|
+
when :core
|
|
170
|
+
uri = "https://" + @config[:api_host_core] + @URI_VERSION + uripath
|
|
171
|
+
when :cma
|
|
172
|
+
uri = "https://" + @config[:api_host_cma] + @URI_VERSION + uripath
|
|
173
|
+
else
|
|
174
|
+
raise "Unknown API (%s) referenced" % api
|
|
175
|
+
end
|
|
176
|
+
uri << "?" + uriparameters if uriparameters
|
|
177
|
+
|
|
178
|
+
##
|
|
179
|
+
# Build the Headers
|
|
180
|
+
|
|
181
|
+
headers = {
|
|
182
|
+
# Authentication headers Access-Key and Sign
|
|
183
|
+
"Access-Key" => @config[:access_key],
|
|
184
|
+
"Sign" => signature
|
|
185
|
+
}
|
|
186
|
+
# Add a Content-type header for application/json if there is a payload body to send
|
|
187
|
+
headers["Content-type"] = "application/json" if body
|
|
188
|
+
|
|
189
|
+
# Call RestClient based on the method against URI with authentication headers "Access-Key" and "Sign"
|
|
190
|
+
retries ||= 0
|
|
191
|
+
response = nil
|
|
192
|
+
begin
|
|
193
|
+
|
|
194
|
+
RestClient::Request.execute(
|
|
195
|
+
method: method,
|
|
196
|
+
url: uri,
|
|
197
|
+
headers: headers,
|
|
198
|
+
payload: body
|
|
199
|
+
) do
|
|
200
|
+
|inner_response, request, result, &block|
|
|
201
|
+
response = inner_response
|
|
202
|
+
|
|
203
|
+
# Debug output
|
|
204
|
+
if $DEBUG
|
|
205
|
+
puts "Request: %s: %s" % [ request.method.upcase, request.uri ]
|
|
206
|
+
puts "Headers: %s" % [ request.headers ]
|
|
207
|
+
puts "POST payload: %s" % [ body ] if body
|
|
208
|
+
puts "Timestamp: %s" % [ Time.now.strftime('%Y-%m-%d %H:%M:%S %z') ]
|
|
209
|
+
puts "Response: %d: %s" % [ response.code, response ]
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
# Check for errors
|
|
213
|
+
self._check_errors( response, error_string )
|
|
214
|
+
|
|
215
|
+
# Success: Convert JSON to Ruby object and return it
|
|
216
|
+
responsebody = JSON.parse(response)['body']
|
|
217
|
+
return responsebody
|
|
218
|
+
end
|
|
219
|
+
rescue => e
|
|
220
|
+
# Rescue any exceptions thrown by _check_errors
|
|
221
|
+
|
|
222
|
+
# Retry 3 times with pauses if temporary error
|
|
223
|
+
puts "Retries: #{retries} | response.code: #{response.code}"
|
|
224
|
+
if response.code == 502 and retries += 1 < 3
|
|
225
|
+
sleep 30
|
|
226
|
+
retry
|
|
227
|
+
end
|
|
228
|
+
|
|
229
|
+
# Output
|
|
230
|
+
puts e.inspect if $DEBUG
|
|
231
|
+
puts e.message if $DEBUG
|
|
232
|
+
|
|
233
|
+
# Pass it on
|
|
234
|
+
raise e
|
|
235
|
+
end
|
|
236
|
+
end
|
|
237
|
+
|
|
238
|
+
##
|
|
239
|
+
# CORE API Venue and Market function methods
|
|
240
|
+
|
|
241
|
+
# Return an array of supported Venues
|
|
242
|
+
def get_venues
|
|
243
|
+
return self._rest_call( :get, :core, "/venues", nil, "Retrieval of supported venues failed." )
|
|
244
|
+
end
|
|
245
|
+
|
|
246
|
+
# Return an array of hashes of a venue's markets
|
|
247
|
+
def get_markets( venue: )
|
|
248
|
+
uripath = "/venues/" + venue + "/markets"
|
|
249
|
+
return self._rest_call( :get, :core, uripath, nil, "Retrieval of supported venue's markets failed." )
|
|
250
|
+
end
|
|
251
|
+
|
|
252
|
+
##
|
|
253
|
+
# CORE API Account function methods
|
|
254
|
+
|
|
255
|
+
# Return a hash of a venue account
|
|
256
|
+
def get_account(
|
|
257
|
+
venue: @context[:venue].to_s,
|
|
258
|
+
accountid: @context[:accountid] )
|
|
259
|
+
|
|
260
|
+
uripath = "/venues/" + venue + "/accounts/" + accountid
|
|
261
|
+
errormsg = "Retrieval of venue account information for account %s failed." % accountid
|
|
262
|
+
return self._rest_call( :get, :core, uripath, nil, errormsg )
|
|
263
|
+
end
|
|
264
|
+
|
|
265
|
+
# Return an array of hashes of a venue account's balances
|
|
266
|
+
def get_account_balance(
|
|
267
|
+
venue: @context[:venue].to_s,
|
|
268
|
+
accountid: @context[:accountid],
|
|
269
|
+
venuekeyid: @context[:venuekeyid] )
|
|
270
|
+
|
|
271
|
+
uripath = "/venues/exchanges/" + venue + "/accounts/" + accountid + "/balance"
|
|
272
|
+
uriparams = "venueKeyId=" + venuekeyid
|
|
273
|
+
errormsg = "Retrieval of venue account's balance for account %s failed." % accountid
|
|
274
|
+
return self._rest_call( :get, :core, uripath, uriparams, errormsg )
|
|
275
|
+
end
|
|
276
|
+
|
|
277
|
+
# Get account balance for symbol
|
|
278
|
+
def get_account_balance_symbol(
|
|
279
|
+
symbol:,
|
|
280
|
+
venue: @context[:venue].to_s,
|
|
281
|
+
accountid: @context[:accountid],
|
|
282
|
+
venuekeyid: @context[:venuekeyid] )
|
|
283
|
+
|
|
284
|
+
symbol = symbol.downcase
|
|
285
|
+
balance = nil
|
|
286
|
+
# TODO: Check cached balance's timestamp, if too old, update balances
|
|
287
|
+
# Retrieve all balances for account
|
|
288
|
+
@balances = self.get_account_balance( venue: venue, accountid: accountid, venuekeyid: venuekeyid )
|
|
289
|
+
# Find the balance for the symbol we want
|
|
290
|
+
@balances["assets"].each do |asset|
|
|
291
|
+
balance = asset["amount"] if asset["currency"]&.downcase == symbol
|
|
292
|
+
end
|
|
293
|
+
# Raise an exception if the requested symbol is not found
|
|
294
|
+
raise "No balance returned for currency symbol \"%s\"" % symbol if ! balance
|
|
295
|
+
# Return balance for requested symbol as a Float
|
|
296
|
+
return balance.to_f
|
|
297
|
+
end
|
|
298
|
+
|
|
299
|
+
# Get account balance for symbol
|
|
300
|
+
def get_account_available_balance_symbol(
|
|
301
|
+
symbol:,
|
|
302
|
+
venue: @context[:venue].to_s,
|
|
303
|
+
accountid: @context[:accountid],
|
|
304
|
+
venuekeyid: @context[:venuekeyid] )
|
|
305
|
+
|
|
306
|
+
symbol = symbol.downcase
|
|
307
|
+
balance = nil
|
|
308
|
+
# TODO: Check cached balance's timestamp, if too old, update balances
|
|
309
|
+
# Retrieve all balances for account
|
|
310
|
+
@balances = self.get_account_balance( venue: venue, accountid: accountid, venuekeyid: venuekeyid )
|
|
311
|
+
# Find the balance for the symbol we want
|
|
312
|
+
@balances["assets"].each do |asset|
|
|
313
|
+
balance = asset["available"] if asset["currency"]&.downcase == symbol
|
|
314
|
+
end
|
|
315
|
+
# Raise an exception if the requested symbol is not found
|
|
316
|
+
raise "No balance returned for currency symbol \"%s\"" % symbol if ! balance
|
|
317
|
+
# Return balance for requested symbol as a Float
|
|
318
|
+
return balance.to_f
|
|
319
|
+
end
|
|
320
|
+
|
|
321
|
+
# Return an array of hashes of a venue account's orders
|
|
322
|
+
# Accepts optional array of orders statuses for filtering results
|
|
323
|
+
def get_account_orders(
|
|
324
|
+
status_filters: [],
|
|
325
|
+
venue: @context[:venue].to_s,
|
|
326
|
+
accountid: @context[:accountid],
|
|
327
|
+
venuekeyid: @context[:venuekeyid] )
|
|
328
|
+
|
|
329
|
+
uripath = "/venues/exchanges/" + venue + "/accounts/" + accountid + "/orders"
|
|
330
|
+
uriparams = "venueKeyId=" + venuekeyid
|
|
331
|
+
status_filters.each do |status|
|
|
332
|
+
uriparams << "&status=" + status.to_s
|
|
333
|
+
end
|
|
334
|
+
errormsg = "Retrieval of venue account's orders for account %s failed." % accountid
|
|
335
|
+
return self._rest_call( :get, :core, uripath, uriparams, errormsg )
|
|
336
|
+
end
|
|
337
|
+
|
|
338
|
+
# Return an hash of a venue account's single order
|
|
339
|
+
def get_account_order(
|
|
340
|
+
orderid:,
|
|
341
|
+
venue: @context[:venue].to_s,
|
|
342
|
+
accountid: @context[:accountid],
|
|
343
|
+
venuekeyid: @context[:venuekeyid] )
|
|
344
|
+
|
|
345
|
+
uripath = "/venues/exchanges/" + venue + "/accounts/" + accountid + "/orders/" + orderid
|
|
346
|
+
uriparams = "venueKeyId=" + venuekeyid
|
|
347
|
+
errormsg = "Retrieval of venue account's order %s for account %s failed." % [orderid, accountid]
|
|
348
|
+
return self._rest_call( :get, :core, uripath, uriparams, errormsg )
|
|
349
|
+
end
|
|
350
|
+
|
|
351
|
+
# Create a new market order under a venue account
|
|
352
|
+
def put_account_order_market(
|
|
353
|
+
market:,
|
|
354
|
+
side:,
|
|
355
|
+
quantity:,
|
|
356
|
+
amount:,
|
|
357
|
+
venue: @context[:venue].to_s,
|
|
358
|
+
accountid: @context[:accountid],
|
|
359
|
+
venuekeyid: @context[:venuekeyid] )
|
|
360
|
+
|
|
361
|
+
uripath = '/venues/exchanges/' + venue + '/accounts/' + accountid + '/orders'
|
|
362
|
+
errormsg = 'Creation of new market order for account %s failed.' % accountid
|
|
363
|
+
body = {
|
|
364
|
+
'venue' => venue,
|
|
365
|
+
'venueKeyId' => venuekeyid,
|
|
366
|
+
'orderType' => 'market',
|
|
367
|
+
'side' => side,
|
|
368
|
+
'market' => market
|
|
369
|
+
}
|
|
370
|
+
body['quantity'] = quantity if quantity
|
|
371
|
+
body['amount'] = amount if amount
|
|
372
|
+
return self._rest_call( :post, :core, uripath, nil, errormsg, body.to_json )
|
|
373
|
+
end
|
|
374
|
+
|
|
375
|
+
# Create a new limit order under a venue account
|
|
376
|
+
def put_account_order_limit(
|
|
377
|
+
market:,
|
|
378
|
+
side:,
|
|
379
|
+
quantity:,
|
|
380
|
+
price:,
|
|
381
|
+
venue: @context[:venue].to_s,
|
|
382
|
+
accountid: @context[:accountid],
|
|
383
|
+
venuekeyid: @context[:venuekeyid] )
|
|
384
|
+
|
|
385
|
+
uripath = "/venues/exchanges/" + venue + "/accounts/" + accountid + "/orders"
|
|
386
|
+
errormsg = "Creation of new limit order for account %s failed." % accountid
|
|
387
|
+
body = {
|
|
388
|
+
"venue" => venue,
|
|
389
|
+
"venueKeyId" => venuekeyid,
|
|
390
|
+
"orderType" => "limit",
|
|
391
|
+
"side" => side,
|
|
392
|
+
"quantity" => quantity,
|
|
393
|
+
"price" => price,
|
|
394
|
+
"market" => market
|
|
395
|
+
}
|
|
396
|
+
return self._rest_call( :post, :core, uripath, nil, errormsg, body.to_json )
|
|
397
|
+
end
|
|
398
|
+
|
|
399
|
+
# Create a new stop loss order under a venue account
|
|
400
|
+
def put_account_order_stop(
|
|
401
|
+
market:,
|
|
402
|
+
side:,
|
|
403
|
+
quantity:,
|
|
404
|
+
triggerprice:,
|
|
405
|
+
amount:,
|
|
406
|
+
venue: @context[:venue].to_s,
|
|
407
|
+
accountid: @context[:accountid],
|
|
408
|
+
venuekeyid: @context[:venuekeyid] )
|
|
409
|
+
|
|
410
|
+
uripath = "/venues/exchanges/" + venue + "/accounts/" + accountid + "/orders"
|
|
411
|
+
errormsg = "Creation of new limit order for account %s failed." % accountid
|
|
412
|
+
body = {
|
|
413
|
+
"venue" => venue,
|
|
414
|
+
"venueKeyId" => venuekeyid,
|
|
415
|
+
"orderType" => "stop",
|
|
416
|
+
"side" => side,
|
|
417
|
+
"triggerPrice" => triggerprice,
|
|
418
|
+
"market" => market
|
|
419
|
+
}
|
|
420
|
+
body['quantity'] = quantity if quantity
|
|
421
|
+
body['amount'] = amount if amount
|
|
422
|
+
return self._rest_call( :post, :core, uripath, nil, errormsg, body.to_json )
|
|
423
|
+
end
|
|
424
|
+
|
|
425
|
+
# Delete (cancel) a venue account's open order
|
|
426
|
+
def del_account_order(
|
|
427
|
+
orderid:,
|
|
428
|
+
venue: @context[:venue].to_s,
|
|
429
|
+
accountid: @context[:accountid],
|
|
430
|
+
venuekeyid: @context[:venuekeyid] )
|
|
431
|
+
|
|
432
|
+
uripath = "/venues/exchanges/" + venue + "/accounts/" + accountid + "/orders/" + orderid
|
|
433
|
+
uriparams = "venueKeyId=" + venuekeyid
|
|
434
|
+
errormsg = "Cancellation of venue account's order %s for account %s failed." % [orderid, accountid]
|
|
435
|
+
return self._rest_call( :delete, :core, uripath, uriparams, errormsg )
|
|
436
|
+
end
|
|
437
|
+
|
|
438
|
+
# Return an array of hashes of a venue account's trades
|
|
439
|
+
def get_account_trades(
|
|
440
|
+
venue: @context[:venue].to_s,
|
|
441
|
+
accountid: @context[:accountid],
|
|
442
|
+
venuekeyid: @context[:venuekeyid] )
|
|
443
|
+
|
|
444
|
+
uripath = "/venues/exchanges/" + venue + "/accounts/" + accountid + "/trades"
|
|
445
|
+
uriparams = "venueKeyId=" + venuekeyid
|
|
446
|
+
errormsg = "Retrieval of venue account's orders for account %s failed." % accountid
|
|
447
|
+
return self._rest_call( :get, :core, uripath, uriparams, errormsg )
|
|
448
|
+
end
|
|
449
|
+
|
|
450
|
+
# Return a hash of a venue account's trade
|
|
451
|
+
def get_account_trade(
|
|
452
|
+
tradeid:,
|
|
453
|
+
venue: @context[:venue].to_s,
|
|
454
|
+
accountid: @context[:accountid],
|
|
455
|
+
venuekeyid: @context[:venuekeyid] )
|
|
456
|
+
|
|
457
|
+
uripath = "/venues/exchanges/" + venue + "/accounts/" + accountid + "/trades/" + tradeid
|
|
458
|
+
uriparams = "venueKeyId=" + venuekeyid
|
|
459
|
+
errormsg = "Retrieval of venue account's trade %s for account %s failed." % [tradeid, accountid]
|
|
460
|
+
return self._rest_call( :get, :core, uripath, uriparams, errormsg )
|
|
461
|
+
end
|
|
462
|
+
|
|
463
|
+
##
|
|
464
|
+
# CMA API Methods
|
|
465
|
+
|
|
466
|
+
# Return a hash of a venue market's order book
|
|
467
|
+
def get_market_orderbook(
|
|
468
|
+
market:,
|
|
469
|
+
side: nil,
|
|
470
|
+
limit: nil,
|
|
471
|
+
venue: @context[:venue].to_s)
|
|
472
|
+
|
|
473
|
+
uripath = "/venues/" + venue + "/markets/" + market + "/orderbook"
|
|
474
|
+
parts = []
|
|
475
|
+
parts << "side=%s" % side if side
|
|
476
|
+
parts << "limit=%d" % limit if limit
|
|
477
|
+
uriparams = parts.empty? ? nil : parts.join("&")
|
|
478
|
+
|
|
479
|
+
return self._rest_call( :get, :cma, uripath, uriparams, "Retrieval of venue market's order book failed." )
|
|
480
|
+
end
|
|
481
|
+
|
|
482
|
+
# Return a hash of a venue market's order book's price at a specified volume
|
|
483
|
+
def get_market_orderbook_depth_price(
|
|
484
|
+
market:,
|
|
485
|
+
side: nil,
|
|
486
|
+
volume: nil,
|
|
487
|
+
cumulative: nil,
|
|
488
|
+
venue: @context[:venue].to_s)
|
|
489
|
+
|
|
490
|
+
uripath = "/venues/" + venue + "/markets/" + market + "/orderbook/depth/price"
|
|
491
|
+
parts = []
|
|
492
|
+
parts << "side=%s" % side if side
|
|
493
|
+
parts << "volume=%f" % volume if volume
|
|
494
|
+
parts << "cumulative=%s" % cumulative.to_s if !cumulative.nil?
|
|
495
|
+
uriparams = parts.empty? ? nil : parts.join("&")
|
|
496
|
+
|
|
497
|
+
return self._rest_call( :get, :cma, uripath, uriparams, "Retrieval of venue market's order book price at given volume (#{volume}) failed." )
|
|
498
|
+
end
|
|
499
|
+
|
|
500
|
+
# Return a hash of a venue market's order book's volume at a specified price
|
|
501
|
+
def get_market_orderbook_depth_volume(
|
|
502
|
+
market:,
|
|
503
|
+
side: nil,
|
|
504
|
+
price: nil,
|
|
505
|
+
cumulative: nil,
|
|
506
|
+
venue: @context[:venue].to_s)
|
|
507
|
+
|
|
508
|
+
uripath = "/venues/" + venue + "/markets/" + market + "/orderbook/depth/volume"
|
|
509
|
+
parts = []
|
|
510
|
+
parts << "side=%s" % side if side
|
|
511
|
+
parts << "price=%f" % price if price
|
|
512
|
+
parts << "cumulative=%s" % cumulative.to_s if !cumulative.nil?
|
|
513
|
+
uriparams = parts.empty? ? nil : parts.join("&")
|
|
514
|
+
|
|
515
|
+
return self._rest_call( :get, :cma, uripath, uriparams, "Retrieval of venue market's order book volume at given price (#{price}) failed." )
|
|
516
|
+
end
|
|
517
|
+
|
|
518
|
+
# Return an array of a venue market's ticker hashes
|
|
519
|
+
def get_market_tickers(
|
|
520
|
+
market:,
|
|
521
|
+
from: (Time.now.to_i - 300) * 1000, # default to last 5 minutes
|
|
522
|
+
to: nil, # to defaults to Time.now within Cryptomnio
|
|
523
|
+
cursor: nil,
|
|
524
|
+
venue: @context[:venue].to_s)
|
|
525
|
+
|
|
526
|
+
uripath = "/venues/" + venue + "/markets/" + market + "/ticker"
|
|
527
|
+
uriparams = "from=%d" % from if from
|
|
528
|
+
uriparams << "&to=%d" % to if to
|
|
529
|
+
uriparams << "&cursor=%d" % cursor if cursor
|
|
530
|
+
|
|
531
|
+
retries = 0
|
|
532
|
+
pause = 4
|
|
533
|
+
begin
|
|
534
|
+
result = self._rest_call( :get, :cma, uripath, uriparams, "Retrieval of venue's market's market ticker failed." )
|
|
535
|
+
pp result if $DEBUG
|
|
536
|
+
raise "Error: Empty response received" if result == nil
|
|
537
|
+
raise "Error: No latest ticker value" if result['latest'] == nil
|
|
538
|
+
return result.dup
|
|
539
|
+
rescue => e
|
|
540
|
+
case
|
|
541
|
+
when retries <= 3
|
|
542
|
+
retries += 1
|
|
543
|
+
sleep pause
|
|
544
|
+
pause = pause * 2
|
|
545
|
+
retry
|
|
546
|
+
else
|
|
547
|
+
raise "Error: Continued error for 3 retries: %s" % e.message
|
|
548
|
+
return false
|
|
549
|
+
end
|
|
550
|
+
end
|
|
551
|
+
end
|
|
552
|
+
|
|
553
|
+
# Return a venue market's most current ticker hash
|
|
554
|
+
def get_market_ticker(
|
|
555
|
+
market:,
|
|
556
|
+
venue: @context[:venue].to_s)
|
|
557
|
+
|
|
558
|
+
# Call get_market_tickers to request most recent ticker
|
|
559
|
+
#TODO: Replace with this call once Cryptomnio accepts calls without a from parameter (don't need a set of tickers, just most recent)
|
|
560
|
+
#tickers = self.get_market_tickers( market: market, venue: venue )
|
|
561
|
+
tickers = self.get_market_tickers( market: market, from: (Time.now.to_i - 30) * 1000, venue: venue )
|
|
562
|
+
|
|
563
|
+
# Check timestamp on ticker and ensure it's not too old
|
|
564
|
+
too_old = (Time.now.to_i - 120) * 1000 # over 2 minutes ago
|
|
565
|
+
raise "ERROR: Most recent ticker is too old! (%s is over 2 minutes ago)" % [ Time.new(tickers['latest']['timestamp'] / 1000) ] if tickers['latest']['timestamp'] < too_old
|
|
566
|
+
|
|
567
|
+
# Output
|
|
568
|
+
puts "Ticker: %s" % tickers['latest']['price'] if $VERBOSE
|
|
569
|
+
|
|
570
|
+
return tickers['latest'].dup
|
|
571
|
+
end
|
|
572
|
+
|
|
573
|
+
# Return a venue market's recent trades array
|
|
574
|
+
def get_market_trades(
|
|
575
|
+
market:,
|
|
576
|
+
limit: nil,
|
|
577
|
+
venue: @context[:venue].to_s )
|
|
578
|
+
|
|
579
|
+
uripath = "/venues/" + venue + "/markets/" + market + "/trades"
|
|
580
|
+
uriparams = limit ? "limit=%s" % limit : nil
|
|
581
|
+
|
|
582
|
+
retries = 0
|
|
583
|
+
begin
|
|
584
|
+
result = self._rest_call( :get, :cma, uripath, uriparams, "Retrieval of venue's market's market ticker failed." )
|
|
585
|
+
|
|
586
|
+
# Raise an execption if the result is null or empty
|
|
587
|
+
raise "Error: Empty set of market trades received" if ! result || result.count < 1
|
|
588
|
+
|
|
589
|
+
# Output
|
|
590
|
+
if $DEBUG
|
|
591
|
+
puts "Trades:\n"
|
|
592
|
+
pp result
|
|
593
|
+
end
|
|
594
|
+
|
|
595
|
+
# Return the trades array
|
|
596
|
+
return result
|
|
597
|
+
rescue => e
|
|
598
|
+
case
|
|
599
|
+
when retries <= 3
|
|
600
|
+
retries += 1
|
|
601
|
+
sleep 1
|
|
602
|
+
retry
|
|
603
|
+
else
|
|
604
|
+
raise "Error: Received empty set of market trades for 3 retries: %s" % e.message
|
|
605
|
+
return false
|
|
606
|
+
end
|
|
607
|
+
end
|
|
608
|
+
|
|
609
|
+
end
|
|
610
|
+
|
|
611
|
+
# Return an array of period data
|
|
612
|
+
def get_market_periods(
|
|
613
|
+
market:,
|
|
614
|
+
periodlength:,
|
|
615
|
+
periodcount: nil, # periodCount defaults to 10 within Cryptomnio
|
|
616
|
+
to: nil, # to defaults to Time.now within Cryptomnio
|
|
617
|
+
limit: nil,
|
|
618
|
+
venue: @context[:venue].to_s)
|
|
619
|
+
|
|
620
|
+
uripath = "/venues/" + venue + "/markets/" + market + "/periods"
|
|
621
|
+
uriparams = "periodLength=%s" % periodlength
|
|
622
|
+
uriparams << "&periodCount=%d" % periodcount if periodcount
|
|
623
|
+
uriparams << "&to=%d" % to if to
|
|
624
|
+
uriparams << "&limit=%d" % limit if limit
|
|
625
|
+
|
|
626
|
+
# TODO: Determine if we have any of the requested periods cached, and instead request only what we don't already have
|
|
627
|
+
|
|
628
|
+
retries = 0
|
|
629
|
+
begin
|
|
630
|
+
result = self._rest_call( :get, :cma, uripath, uriparams, "Retrieval of venue's market's market periods failed." )
|
|
631
|
+
|
|
632
|
+
# Raise an execption if the result is null or empty
|
|
633
|
+
raise "Error: Empty set of market periods received" if ! result || result.count < 1
|
|
634
|
+
|
|
635
|
+
# Output
|
|
636
|
+
if $DEBUG
|
|
637
|
+
puts "Periods:\n"
|
|
638
|
+
pp result
|
|
639
|
+
end
|
|
640
|
+
|
|
641
|
+
# Return the periods array
|
|
642
|
+
return result
|
|
643
|
+
rescue => e
|
|
644
|
+
case
|
|
645
|
+
when retries <= 3
|
|
646
|
+
retries += 1
|
|
647
|
+
sleep 1
|
|
648
|
+
retry
|
|
649
|
+
else
|
|
650
|
+
raise "Error: Received empty set of market periods for 3 retries: %s" % e.message
|
|
651
|
+
return false
|
|
652
|
+
end
|
|
653
|
+
end
|
|
654
|
+
end
|
|
655
|
+
|
|
656
|
+
# Return a momentum value for a defined period of time
|
|
657
|
+
def get_market_momentum(
|
|
658
|
+
market:,
|
|
659
|
+
type:,
|
|
660
|
+
periodlength:,
|
|
661
|
+
periodcount: nil, # periodCount defaults to 10 within Cryptomnio
|
|
662
|
+
to: nil, # to defaults to Time.now within Cryptomnio
|
|
663
|
+
venue: @context[:venue].to_s)
|
|
664
|
+
|
|
665
|
+
uripath = "/venues/" + venue + "/markets/" + market + "/momentum"
|
|
666
|
+
uriparams = "type=%s" % type
|
|
667
|
+
uriparams << "&periodLength=%s" % periodlength
|
|
668
|
+
uriparams << "&periodCount=%d" % periodcount if periodcount
|
|
669
|
+
uriparams << "&to=%d" % to if to
|
|
670
|
+
|
|
671
|
+
retries = 0
|
|
672
|
+
begin
|
|
673
|
+
result = self._rest_call( :get, :cma, uripath, uriparams, "Retrieval of venue's market's market momentum failed." )
|
|
674
|
+
|
|
675
|
+
# Raise an execption if the result is null or empty
|
|
676
|
+
raise "Error: Empty set of market momentum received" if ! result || result.count < 1
|
|
677
|
+
|
|
678
|
+
# Output
|
|
679
|
+
puts "Momentum: %f\n" % result['price_change'].to_f if $DEBUG
|
|
680
|
+
|
|
681
|
+
# Return momentum value
|
|
682
|
+
return result['price_change'].to_f
|
|
683
|
+
rescue => e
|
|
684
|
+
case
|
|
685
|
+
when retries <= 3
|
|
686
|
+
retries += 1
|
|
687
|
+
sleep 1
|
|
688
|
+
retry
|
|
689
|
+
else
|
|
690
|
+
raise "Error: Received empty set of market momentum for 3 retries: %s" % e.message
|
|
691
|
+
return false
|
|
692
|
+
end
|
|
693
|
+
end
|
|
694
|
+
end
|
|
695
|
+
|
|
696
|
+
# Return an exponential moving average value for a defined period of time
|
|
697
|
+
def get_market_ema(
|
|
698
|
+
market:,
|
|
699
|
+
type:,
|
|
700
|
+
periodlength:,
|
|
701
|
+
periodcalc:,
|
|
702
|
+
periodcount: nil, # periodCount defaults to 10 within Cryptomnio
|
|
703
|
+
to: nil, # to defaults to Time.now within Cryptomnio
|
|
704
|
+
venue: @context[:venue].to_s)
|
|
705
|
+
|
|
706
|
+
uripath = "/venues/" + venue + "/markets/" + market + "/averages/ema"
|
|
707
|
+
uriparams = "type=%s" % type
|
|
708
|
+
uriparams << "&periodLength=%s" % periodlength
|
|
709
|
+
uriparams << "&periodCalc=%d" % periodcalc
|
|
710
|
+
uriparams << "&periodCount=%d" % periodcount if periodcount
|
|
711
|
+
uriparams << "&to=%d" % to if to
|
|
712
|
+
|
|
713
|
+
retries = 0
|
|
714
|
+
begin
|
|
715
|
+
result = self._rest_call( :get, :cma, uripath, uriparams, "Retrieval of venue's market's market Exponential Moving Average (EMA) failed." )
|
|
716
|
+
|
|
717
|
+
# Raise an execption if the result is null or empty
|
|
718
|
+
raise "Error: Empty set of market Exponential Moving Average (EMA) received" if ! result || result.count < 1
|
|
719
|
+
|
|
720
|
+
# Output
|
|
721
|
+
if $DEBUG
|
|
722
|
+
puts "EMA sets:\n"
|
|
723
|
+
pp result
|
|
724
|
+
end
|
|
725
|
+
|
|
726
|
+
# Return averages array
|
|
727
|
+
return result
|
|
728
|
+
rescue => e
|
|
729
|
+
case
|
|
730
|
+
when retries <= 3
|
|
731
|
+
retries += 1
|
|
732
|
+
sleep 1
|
|
733
|
+
retry
|
|
734
|
+
else
|
|
735
|
+
raise "Error: Received empty set of market Exponential Moving Average (EMA) for 3 retries: %s" % e.message
|
|
736
|
+
return false
|
|
737
|
+
end
|
|
738
|
+
end
|
|
739
|
+
|
|
740
|
+
end
|
|
741
|
+
|
|
742
|
+
##
|
|
743
|
+
# Object Methods
|
|
744
|
+
|
|
745
|
+
# Object initialization
|
|
746
|
+
def initialize
|
|
747
|
+
yield self if block_given?
|
|
748
|
+
super
|
|
749
|
+
|
|
750
|
+
##
|
|
751
|
+
# Configuration check
|
|
752
|
+
|
|
753
|
+
# Check for required API hostnames
|
|
754
|
+
raise "Missing Configuration Parameter: api_host_core" if ! @config[:api_host_core]
|
|
755
|
+
raise "Missing Configuration Parameter: api_host_cma" if ! @config[:api_host_cma]
|
|
756
|
+
|
|
757
|
+
# Check for required Authentication Credentials
|
|
758
|
+
raise "Missing Configuration Parameter: authtype" if ! @config[:authtype]
|
|
759
|
+
case @config[:authtype]
|
|
760
|
+
when 'Basic'
|
|
761
|
+
raise "Missing Configuration Parameter: username" if ! @config[:username]
|
|
762
|
+
raise "Missing Configuration Parameter: password" if ! @config[:password]
|
|
763
|
+
when 'Cryptomnio'
|
|
764
|
+
raise "Missing Configuration Parameter: access_key" if ! @config[:access_key]
|
|
765
|
+
raise "Missing Configuration Parameter: secret_key" if ! @config[:secret_key]
|
|
766
|
+
else
|
|
767
|
+
raise "Unrecognized Authentication Type: %s" % @config[:authtype]
|
|
768
|
+
end
|
|
769
|
+
|
|
770
|
+
# Check for at least one contexts (venue+account)
|
|
771
|
+
raise "Missing Configuration Paramters: contexts" if @config[:contexts].count < 1
|
|
772
|
+
|
|
773
|
+
# Check configured contexts and validate them by switching to them
|
|
774
|
+
puts "Checking all configured contexts (venue + account) for required parameters" if $DEBUG
|
|
775
|
+
@config[:contexts].each do | label, paramhash |
|
|
776
|
+
self.context_switch( label )
|
|
777
|
+
end
|
|
778
|
+
|
|
779
|
+
# Switch back to the first context by default
|
|
780
|
+
puts "Switching back to the first context by default" if $DEBUG
|
|
781
|
+
label = @config[:contexts].keys[0]
|
|
782
|
+
self.context_switch( label )
|
|
783
|
+
|
|
784
|
+
# Output
|
|
785
|
+
if $DEBUG
|
|
786
|
+
puts "Cryptomnio Startup Configuration:"
|
|
787
|
+
p @config
|
|
788
|
+
end
|
|
789
|
+
end
|
|
790
|
+
|
|
791
|
+
##
|
|
792
|
+
# Utility Methods
|
|
793
|
+
|
|
794
|
+
# Error-handling method
|
|
795
|
+
def _check_errors( response, thisfailure )
|
|
796
|
+
case
|
|
797
|
+
when response.code == 200
|
|
798
|
+
# Success!
|
|
799
|
+
return true
|
|
800
|
+
when response.code == 400
|
|
801
|
+
# Bad Request
|
|
802
|
+
raise "Error: [%d]: %s" % [ response.code, response ]
|
|
803
|
+
return false
|
|
804
|
+
when response.code == 401
|
|
805
|
+
# Unauthorized
|
|
806
|
+
case @config[:authtype]
|
|
807
|
+
when "Basic"
|
|
808
|
+
raise "Cryptomnio Basic Authentication for user %s failed: [%d]: %s" % [ @config[:username], response.code, response ]
|
|
809
|
+
return false
|
|
810
|
+
when "Cryptomnio"
|
|
811
|
+
raise "Cryptomnio Key Authentication failed: [%d]: %s" % [ response.code, response ]
|
|
812
|
+
return false
|
|
813
|
+
else
|
|
814
|
+
raise "Authentication failed: [%d]: %s" % [ response.code, response ]
|
|
815
|
+
return false
|
|
816
|
+
end
|
|
817
|
+
when response.code == 404
|
|
818
|
+
# Not Found
|
|
819
|
+
raise "Error: Missing Resource"
|
|
820
|
+
return false
|
|
821
|
+
when response.code == 502
|
|
822
|
+
# Bad Gateway
|
|
823
|
+
raise "Error: %s: [%d]: %s" % [ thisfailure, response.code, response ]
|
|
824
|
+
when response.code == 503
|
|
825
|
+
# Service Unavailable
|
|
826
|
+
raise "Temporary Error: %s: [%d]: %s" % [ thisfailure, response.code, response ]
|
|
827
|
+
return false
|
|
828
|
+
when response.code == 504
|
|
829
|
+
# Gateway Timeout (temporary error)
|
|
830
|
+
raise "Temporary Error: %s: [%d]: %s" % [ thisfailure, response.code, response ]
|
|
831
|
+
return false
|
|
832
|
+
else
|
|
833
|
+
# Catch-all: Anything other than the above
|
|
834
|
+
raise "Error: %s: [%d]: %s" % [ thisfailure, response.code, response ]
|
|
835
|
+
return false
|
|
836
|
+
end
|
|
837
|
+
end
|
|
838
|
+
|
|
839
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: cryptomnio
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.2.2
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Dustin D. Trammell
|
|
8
|
+
bindir: bin
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 2026-10-02 00:00:00.000000000 Z
|
|
11
|
+
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: rest-client
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - "~>"
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: '2.1'
|
|
19
|
+
type: :runtime
|
|
20
|
+
prerelease: false
|
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
+
requirements:
|
|
23
|
+
- - "~>"
|
|
24
|
+
- !ruby/object:Gem::Version
|
|
25
|
+
version: '2.1'
|
|
26
|
+
description: A Ruby gem providing an interface to the Cryptomnio API
|
|
27
|
+
email: info@cryptomnio.com
|
|
28
|
+
executables: []
|
|
29
|
+
extensions: []
|
|
30
|
+
extra_rdoc_files: []
|
|
31
|
+
files:
|
|
32
|
+
- LICENSE
|
|
33
|
+
- lib/cryptomnio.rb
|
|
34
|
+
- lib/cryptomnio/version.rb
|
|
35
|
+
homepage: https://github.com/dtrammell/ruby-cryptomnio
|
|
36
|
+
licenses:
|
|
37
|
+
- MIT
|
|
38
|
+
metadata: {}
|
|
39
|
+
rdoc_options: []
|
|
40
|
+
require_paths:
|
|
41
|
+
- lib
|
|
42
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
43
|
+
requirements:
|
|
44
|
+
- - ">="
|
|
45
|
+
- !ruby/object:Gem::Version
|
|
46
|
+
version: '3.3'
|
|
47
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
48
|
+
requirements:
|
|
49
|
+
- - ">="
|
|
50
|
+
- !ruby/object:Gem::Version
|
|
51
|
+
version: '0'
|
|
52
|
+
requirements: []
|
|
53
|
+
rubygems_version: 4.0.20
|
|
54
|
+
specification_version: 4
|
|
55
|
+
summary: Cryptomnio API Interface
|
|
56
|
+
test_files: []
|