iota-ruby 1.1.8-java

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (64) hide show
  1. checksums.yaml +7 -0
  2. data/.editorconfig +8 -0
  3. data/.gitignore +15 -0
  4. data/.travis.yml +24 -0
  5. data/.yardopts +7 -0
  6. data/CHANGELOG.md +18 -0
  7. data/Gemfile +3 -0
  8. data/LICENSE +21 -0
  9. data/README.md +121 -0
  10. data/Rakefile +36 -0
  11. data/bin/iota-console +15 -0
  12. data/examples/multisig.rb +69 -0
  13. data/ext/ccurl/ccurl.c +134 -0
  14. data/ext/ccurl/extconf.rb +22 -0
  15. data/ext/jcurl/JCurl.java +126 -0
  16. data/ext/jcurl/JCurlService.java +36 -0
  17. data/ext/pow/ccurl-0.3.0.dll +0 -0
  18. data/ext/pow/libccurl-0.3.0.dylib +0 -0
  19. data/ext/pow/libccurl-0.3.0.so +0 -0
  20. data/iota-ruby.gemspec +37 -0
  21. data/lib/iota.rb +76 -0
  22. data/lib/iota/api/api.rb +251 -0
  23. data/lib/iota/api/commands.rb +113 -0
  24. data/lib/iota/api/transport.rb +43 -0
  25. data/lib/iota/api/wrappers.rb +429 -0
  26. data/lib/iota/crypto/bundle.rb +163 -0
  27. data/lib/iota/crypto/converter.rb +244 -0
  28. data/lib/iota/crypto/curl.rb +18 -0
  29. data/lib/iota/crypto/curl_c.rb +17 -0
  30. data/lib/iota/crypto/curl_java.rb +18 -0
  31. data/lib/iota/crypto/curl_ruby.rb +70 -0
  32. data/lib/iota/crypto/hmac.rb +27 -0
  33. data/lib/iota/crypto/kerl.rb +82 -0
  34. data/lib/iota/crypto/pow_provider.rb +27 -0
  35. data/lib/iota/crypto/private_key.rb +80 -0
  36. data/lib/iota/crypto/sha3_ruby.rb +122 -0
  37. data/lib/iota/crypto/signing.rb +97 -0
  38. data/lib/iota/models/account.rb +489 -0
  39. data/lib/iota/models/base.rb +13 -0
  40. data/lib/iota/models/bundle.rb +87 -0
  41. data/lib/iota/models/input.rb +38 -0
  42. data/lib/iota/models/seed.rb +33 -0
  43. data/lib/iota/models/transaction.rb +52 -0
  44. data/lib/iota/models/transfer.rb +44 -0
  45. data/lib/iota/multisig/address.rb +41 -0
  46. data/lib/iota/multisig/multisig.rb +244 -0
  47. data/lib/iota/utils/ascii.rb +50 -0
  48. data/lib/iota/utils/broker.rb +124 -0
  49. data/lib/iota/utils/input_validator.rb +149 -0
  50. data/lib/iota/utils/object_validator.rb +34 -0
  51. data/lib/iota/utils/utils.rb +324 -0
  52. data/lib/iota/version.rb +3 -0
  53. data/lib/jcurl.jar +0 -0
  54. data/lib/patch.rb +17 -0
  55. data/test/ascii_test.rb +114 -0
  56. data/test/curl_c_test.rb +31 -0
  57. data/test/curl_java_test.rb +31 -0
  58. data/test/curl_ruby_test.rb +27 -0
  59. data/test/kerl_test.rb +52 -0
  60. data/test/pow_provider_test.rb +36 -0
  61. data/test/sha3_test.rb +71 -0
  62. data/test/test_helper.rb +4 -0
  63. data/test/utils_test.rb +179 -0
  64. metadata +183 -0
@@ -0,0 +1,22 @@
1
+ can_compile_extensions = false
2
+
3
+ begin
4
+ require 'mkmf'
5
+ can_compile_extensions = true
6
+ rescue Exception
7
+ # This will appear only in verbose mode.
8
+ $stderr.puts "Could not require 'mkmf'. Not fatal, the extensions are optional."
9
+ end
10
+
11
+ if can_compile_extensions && have_header('ruby.h')
12
+ extension_name = 'ccurl'
13
+ dir_config(extension_name) # The destination
14
+ create_makefile(extension_name) # Create Makefile
15
+ else
16
+ # Create a dummy Makefile, to satisfy Gem::Installer#install
17
+ mfile = open("Makefile", "wb")
18
+ mfile.puts '.PHONY: install'
19
+ mfile.puts 'install:'
20
+ mfile.puts "\t" + '@echo "Extensions not installed, falling back to pure Ruby version."'
21
+ mfile.close
22
+ end
@@ -0,0 +1,126 @@
1
+ package com.vmarakana;
2
+
3
+ import java.util.Arrays;
4
+
5
+ import org.jruby.Ruby;
6
+ import org.jruby.RubyArray;
7
+ import org.jruby.RubyClass;
8
+ import org.jruby.RubyInteger;
9
+ import org.jruby.RubyNil;
10
+ import org.jruby.RubyObject;
11
+ import org.jruby.anno.JRubyMethod;
12
+ import org.jruby.runtime.ThreadContext;
13
+ import org.jruby.runtime.builtin.IRubyObject;
14
+
15
+ public class JCurl extends RubyObject {
16
+ /**
17
+ * The hash length.
18
+ */
19
+ public static final int HASH_LENGTH = 243;
20
+ private static final int STATE_LENGTH = 3 * HASH_LENGTH;
21
+
22
+ public static final int NUMBER_OF_ROUNDS = 81;
23
+ private int numberOfRounds;
24
+
25
+ private static final int[] TRUTH_TABLE = {1, 0, -1, 2, 1, -1, 0, 2, -1, 1, 0};
26
+ private final int[] scratchpad = new int[STATE_LENGTH];
27
+ private int[] state;
28
+
29
+ /**
30
+ * Java constructor
31
+ * @param ruby Ruby
32
+ * @param metaclass RubyClass
33
+ */
34
+ public JCurl(Ruby ruby, RubyClass rubyClass) {
35
+ super(ruby, rubyClass);
36
+ }
37
+
38
+ /**
39
+ *
40
+ * @param context ThreadContext
41
+ * @param klass IRubyObject
42
+ * @param args optional (no args rounds = NUMBER_OF_ROUNDS)
43
+ * @return new Vec3 object (ruby)
44
+ */
45
+ @JRubyMethod(name = "new", meta = true, rest = true)
46
+ public static IRubyObject rbNew(ThreadContext context, IRubyObject klass, IRubyObject... args) {
47
+ JCurl jcurl = (JCurl) ((RubyClass) klass).allocate();
48
+ jcurl.init(context, args);
49
+ return jcurl;
50
+ }
51
+
52
+ // This method is internal and not exposed
53
+ private IRubyObject init(ThreadContext context, IRubyObject... args) {
54
+ state = new int[STATE_LENGTH];
55
+
56
+ // Set rounds
57
+ if (args.length > 0 && args[0] instanceof RubyInteger) {
58
+ numberOfRounds = ((RubyInteger) args[0]).getIntValue();
59
+ } else {
60
+ numberOfRounds = NUMBER_OF_ROUNDS;
61
+ }
62
+
63
+ return new RubyNil(context.runtime);
64
+ }
65
+
66
+ @JRubyMethod
67
+ public IRubyObject transform(ThreadContext context) {
68
+ int scratchpadIndex = 0;
69
+ int prev_scratchpadIndex = 0;
70
+ for (int round = 0; round < numberOfRounds; round++) {
71
+ System.arraycopy(state, 0, scratchpad, 0, STATE_LENGTH);
72
+ for (int stateIndex = 0; stateIndex < STATE_LENGTH; stateIndex++) {
73
+ prev_scratchpadIndex = scratchpadIndex;
74
+ if (scratchpadIndex < 365) {
75
+ scratchpadIndex += 364;
76
+ } else {
77
+ scratchpadIndex += -365;
78
+ }
79
+ state[stateIndex] = TRUTH_TABLE[scratchpad[prev_scratchpadIndex] + (scratchpad[scratchpadIndex] << 2) + 5];
80
+ }
81
+ }
82
+
83
+ return new RubyNil(context.runtime);
84
+ }
85
+
86
+ @JRubyMethod
87
+ public IRubyObject absorb(ThreadContext context, final IRubyObject trits) {
88
+ int offset = 0;
89
+ int length = ((RubyArray) trits).getLength();
90
+
91
+ do {
92
+ System.arraycopy(trits.toJava(int[].class), offset, state, 0, length < HASH_LENGTH ? length : HASH_LENGTH);
93
+ transform(context);
94
+ offset += HASH_LENGTH;
95
+ } while ((length -= HASH_LENGTH) > 0);
96
+
97
+ return new RubyNil(context.runtime);
98
+ }
99
+
100
+ @JRubyMethod
101
+ public RubyNil squeeze(ThreadContext context, final IRubyObject trits) {
102
+ int offset = 0;
103
+ int length = ((RubyArray) trits).getLength();
104
+
105
+ do {
106
+ for(; length < HASH_LENGTH; length++) {
107
+ ((RubyArray) trits).append(context.runtime.newFixnum(0));
108
+ }
109
+
110
+ for (int i = 0; i < HASH_LENGTH; i++) {
111
+ ((RubyArray) trits).store(i, context.runtime.newFixnum(state[i]));
112
+ }
113
+
114
+ transform(context);
115
+ offset += HASH_LENGTH;
116
+ } while ((length -= HASH_LENGTH) > 0);
117
+
118
+ return new RubyNil(context.runtime);
119
+ }
120
+
121
+ @JRubyMethod
122
+ public IRubyObject reset(ThreadContext context) {
123
+ Arrays.fill(state, 0);
124
+ return new RubyNil(context.runtime);
125
+ }
126
+ }
@@ -0,0 +1,36 @@
1
+ package com.vmarakana;
2
+
3
+ import java.io.IOException;
4
+
5
+ import org.jruby.Ruby;
6
+ import org.jruby.RubyClass;
7
+ import org.jruby.RubyModule;
8
+ import org.jruby.runtime.ObjectAllocator;
9
+ import org.jruby.runtime.builtin.IRubyObject;
10
+ import org.jruby.runtime.load.BasicLibraryService;
11
+
12
+ public class JCurlService implements BasicLibraryService {
13
+ private Ruby runtime;
14
+
15
+ /**
16
+ * Basic load method of the BasicLibraryService, this method is
17
+ * invoked when the ruby code does the related require call.
18
+ * @param ruby An instance of the JRuby runtime.
19
+ * @return boolean True if everything was successful, false otherwise.
20
+ * @throws IOException is required to match the BasicLibraryService signature
21
+ */
22
+
23
+ @Override
24
+ public boolean basicLoad(final Ruby ruby) throws IOException {
25
+ RubyModule iota = ruby.defineModule("IOTA");
26
+ RubyModule crypto = iota.defineModuleUnder("Crypto");
27
+ RubyClass jcurl = crypto.defineClassUnder("JCurl", ruby.getObject(), new ObjectAllocator() {
28
+ public IRubyObject allocate(Ruby ruby1, RubyClass rubyClass) {
29
+ return new JCurl(ruby1, rubyClass);
30
+ }
31
+ });
32
+
33
+ jcurl.defineAnnotatedMethods(JCurl.class);
34
+ return true;
35
+ }
36
+ }
Binary file
Binary file
Binary file
data/iota-ruby.gemspec ADDED
@@ -0,0 +1,37 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path("../lib", __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require "iota/version"
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "iota-ruby"
8
+ spec.version = IOTA::VERSION
9
+ spec.authors = ["Vivek Marakana"]
10
+ spec.email = ["vivek.marakana@gmail.com"]
11
+
12
+ spec.summary = "IOTA API wrapper for Ruby"
13
+ spec.description = "Ruby gem for the IOTA core"
14
+ spec.homepage = "https://github.com/vivekmarakana/iota.lib.rb"
15
+
16
+ spec.files = `git ls-files`.split("\n") - [ "lib/jcurl.jar"]
17
+ spec.test_files = `git ls-files -- test/*`.split("\n")
18
+ spec.require_paths = ["lib"]
19
+
20
+ if RUBY_PLATFORM =~ /java/
21
+ spec.platform = "java"
22
+ spec.files << "lib/jcurl.jar"
23
+ else
24
+ spec.extensions = ["ext/ccurl/extconf.rb"]
25
+ end
26
+
27
+ spec.add_development_dependency "bundler", ">= 1.15"
28
+ spec.add_development_dependency "rake", ">= 10.0"
29
+ spec.add_development_dependency "minitest", ">= 5.0"
30
+ spec.add_development_dependency "rake-compiler", ">= 1.0.4"
31
+
32
+ unless RUBY_PLATFORM =~ /java/
33
+ spec.add_runtime_dependency "digest-sha3", "~> 1.1"
34
+ end
35
+
36
+ spec.add_runtime_dependency "ffi", "~> 1.9.25"
37
+ end
data/lib/iota.rb ADDED
@@ -0,0 +1,76 @@
1
+ require "patch"
2
+ require "iota/version"
3
+
4
+ require "iota/utils/input_validator"
5
+ require "iota/utils/object_validator"
6
+ require "iota/utils/ascii"
7
+ require "iota/utils/utils"
8
+ require "iota/utils/broker"
9
+
10
+ require "iota/api/commands"
11
+ require "iota/api/wrappers"
12
+ require "iota/api/transport"
13
+ require "iota/api/api"
14
+
15
+ require "iota/crypto/pow_provider"
16
+ require "iota/crypto/curl"
17
+ require "iota/crypto/kerl"
18
+ require "iota/crypto/converter"
19
+ require "iota/crypto/bundle"
20
+ require "iota/crypto/signing"
21
+ require "iota/crypto/hmac"
22
+ require "iota/crypto/private_key"
23
+
24
+ require "iota/multisig/address"
25
+ require "iota/multisig/multisig"
26
+
27
+ require "iota/models/base"
28
+ require "iota/models/input"
29
+ require "iota/models/transfer"
30
+ require "iota/models/seed"
31
+ require "iota/models/transaction"
32
+ require "iota/models/bundle"
33
+ require "iota/models/account"
34
+
35
+ module IOTA
36
+ class Client
37
+ attr_reader :version, :host, :port, :provider, :sandbox, :token, :broker, :api, :utils, :validator, :multisig, :batch_size
38
+
39
+ def initialize(settings = {})
40
+ setSettings(settings)
41
+ @utils = IOTA::Utils::Utils.new
42
+ @validator = @utils.validator
43
+ @multisig = IOTA::Multisig::Multisig.new(self)
44
+ end
45
+
46
+ def changeNode(settings = {})
47
+ setSettings(settings)
48
+ self
49
+ end
50
+
51
+ private
52
+ def setSettings(settings)
53
+ settings = symbolize_keys(settings)
54
+ @host = settings[:host] ? settings[:host] : "http://localhost"
55
+ @port = settings[:port] ? settings[:port] : 14265
56
+ @provider = settings[:provider] || @host.gsub(/\/$/, '') + ":" + @port.to_s
57
+ @sandbox = settings[:sandbox] || false
58
+ @token = settings[:token] || false
59
+ @timeout = settings[:timeout] || 120
60
+ @batch_size = settings[:batch_size] || 500
61
+ @local_pow = settings[:local_pow] || false
62
+
63
+ if @sandbox
64
+ @sandbox = @provider.gsub(/\/$/, '')
65
+ @provider = @sandbox + '/commands'
66
+ end
67
+
68
+ @broker = IOTA::Utils::Broker.new(@provider, @token, @timeout, user: settings[:user], password: settings[:password])
69
+ @api = IOTA::API::Api.new(@broker, @sandbox, @batch_size, @local_pow)
70
+ end
71
+
72
+ def symbolize_keys(hash)
73
+ hash.inject({}){ |h,(k,v)| h[k.to_sym] = v; h }
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,251 @@
1
+ module IOTA
2
+ module API
3
+ class Api
4
+ include Wrappers
5
+ include Transport
6
+
7
+ attr_reader :pow_provider
8
+
9
+ def initialize(broker, sandbox, batch_size = 500, local_pow = false)
10
+ @broker = broker
11
+ @sandbox = sandbox
12
+ @commands = Commands.new
13
+ @utils = IOTA::Utils::Utils.new
14
+ @validator = @utils.validator
15
+ @batch_size = batch_size
16
+ @pow_provider = local_pow ? IOTA::Crypto::PowProvider.new : nil
17
+ end
18
+
19
+ def findTransactions(searchValues, &callback)
20
+ if !@validator.isObject(searchValues)
21
+ return sendData(false, "You have provided an invalid key value", &callback)
22
+ end
23
+
24
+ searchKeys = searchValues.keys
25
+ validKeys = ['bundles', 'addresses', 'tags', 'approvees']
26
+
27
+ error = false
28
+ entry_count = 0
29
+
30
+ searchKeys.each do |key|
31
+ if !validKeys.include?(key.to_s)
32
+ error = "You have provided an invalid key value"
33
+ break
34
+ end
35
+
36
+ hashes = searchValues[key]
37
+ entry_count += hashes.count
38
+
39
+ if key.to_s == 'addresses'
40
+ searchValues[key] = hashes.map do |address|
41
+ @utils.noChecksum(address)
42
+ end
43
+ end
44
+
45
+ # If tags, append to 27 trytes
46
+ if key.to_s == 'tags'
47
+ searchValues[key] = hashes.map do |hash|
48
+ # Simple padding to 27 trytes
49
+ while hash.length < 27
50
+ hash += '9'
51
+ end
52
+ # validate hash
53
+ if !@validator.isTrytes(hash, 27)
54
+ error = "Invalid Trytes provided"
55
+ break
56
+ end
57
+
58
+ hash
59
+ end
60
+ else
61
+ # Check if correct array of hashes
62
+ if !@validator.isArrayOfHashes(hashes)
63
+ error = "Invalid Trytes provided"
64
+ break
65
+ end
66
+ end
67
+ end
68
+
69
+ if error
70
+ return sendData(false, error, &callback)
71
+ else
72
+ if entry_count <= @batch_size || searchKeys.count > 1
73
+ return sendCommand(@commands.findTransactions(searchValues), &callback)
74
+ else
75
+ return sendBatchedCommand(@commands.findTransactions(searchValues), &callback)
76
+ end
77
+ end
78
+ end
79
+
80
+ def getBalances(addresses, threshold, &callback)
81
+ if !@validator.isArrayOfHashes(addresses)
82
+ return sendData(false, "Invalid Trytes provided", &callback)
83
+ end
84
+
85
+ command = @commands.getBalances(addresses.map{|address| @utils.noChecksum(address)}, threshold)
86
+ sendBatchedCommand(command, &callback)
87
+ end
88
+
89
+ def getTrytes(hashes, &callback)
90
+ if !@validator.isArrayOfHashes(hashes)
91
+ return sendData(false, "Invalid Trytes provided", &callback)
92
+ end
93
+
94
+ sendBatchedCommand(@commands.getTrytes(hashes), &callback)
95
+ end
96
+
97
+ def getInclusionStates(transactions, tips, &callback)
98
+ if !@validator.isArrayOfHashes(transactions) || !@validator.isArrayOfHashes(tips)
99
+ return sendData(false, "Invalid Trytes provided", &callback)
100
+ end
101
+
102
+ sendBatchedCommand(@commands.getInclusionStates(transactions, tips), &callback)
103
+ end
104
+
105
+ def getNodeInfo(&callback)
106
+ sendCommand(@commands.getNodeInfo, &callback)
107
+ end
108
+
109
+ def getNeighbors(&callback)
110
+ sendCommand(@commands.getNeighbors, &callback)
111
+ end
112
+
113
+ def addNeighbors(uris, &callback)
114
+ (0...uris.length).step(1) do |i|
115
+ return sendData(false, "You have provided an invalid URI for your Neighbor: " + uris[i], &callback) if !@validator.isUri(uris[i])
116
+ end
117
+
118
+ sendCommand(@commands.addNeighbors(uris), &callback)
119
+ end
120
+
121
+ def removeNeighbors(uris, &callback)
122
+ (0...uris.length).step(1) do |i|
123
+ return sendData(false, "You have provided an invalid URI for your Neighbor: " + uris[i], &callback) if !@validator.isUri(uris[i])
124
+ end
125
+
126
+ sendCommand(@commands.removeNeighbors(uris), &callback)
127
+ end
128
+
129
+ def getTips(&callback)
130
+ sendCommand(@commands.getTips, &callback)
131
+ end
132
+
133
+ def getTransactionsToApprove(depth, reference = nil, &callback)
134
+ # Check if correct depth
135
+ if !@validator.isValue(depth)
136
+ return sendData(false, "Invalid inputs provided", &callback)
137
+ end
138
+
139
+ sendCommand(@commands.getTransactionsToApprove(depth, reference), &callback)
140
+ end
141
+
142
+ def attachToTangle(trunkTransaction, branchTransaction, minWeightMagnitude, trytes, &callback)
143
+ # Check if correct trunk
144
+ if !@validator.isHash(trunkTransaction)
145
+ return sendData(false, "You have provided an invalid hash as a trunk: #{trunkTransaction}", &callback)
146
+ end
147
+
148
+ # Check if correct branch
149
+ if !@validator.isHash(branchTransaction)
150
+ return sendData(false, "You have provided an invalid hash as a branch: #{branchTransaction}", &callback)
151
+ end
152
+
153
+ # Check if minweight is integer
154
+ if !@validator.isValue(minWeightMagnitude)
155
+ return sendData(false, "Invalid minWeightMagnitude provided", &callback)
156
+ end
157
+
158
+ # Check if array of trytes
159
+ if !@validator.isArrayOfTrytes(trytes)
160
+ return sendData(false, "Invalid Trytes provided", &callback)
161
+ end
162
+
163
+ if @pow_provider.nil?
164
+ command = @commands.attachToTangle(trunkTransaction, branchTransaction, minWeightMagnitude, trytes)
165
+
166
+ sendCommand(command, &callback)
167
+ else
168
+ previousTxHash = nil
169
+ finalBundleTrytes = []
170
+
171
+ trytes.each do |current_trytes|
172
+ txObject = @utils.transactionObject(current_trytes)
173
+
174
+ if !previousTxHash
175
+ if txObject.lastIndex != txObject.currentIndex
176
+ return sendData(false, "Wrong bundle order. The bundle should be ordered in descending order from currentIndex", &callback)
177
+ end
178
+
179
+ txObject.trunkTransaction = trunkTransaction
180
+ txObject.branchTransaction = branchTransaction
181
+ else
182
+ txObject.trunkTransaction = previousTxHash
183
+ txObject.branchTransaction = trunkTransaction
184
+ end
185
+
186
+ txObject.attachmentTimestamp = (Time.now.to_f * 1000).to_i
187
+ txObject.attachmentTimestampLowerBound = 0
188
+ txObject.attachmentTimestampUpperBound = (3**27 - 1) / 2
189
+
190
+ newTrytes = @utils.transactionTrytes(txObject)
191
+
192
+ begin
193
+ returnedTrytes = @pow_provider.pow(newTrytes, minWeightMagnitude)
194
+
195
+ newTxObject= @utils.transactionObject(returnedTrytes)
196
+ previousTxHash = newTxObject.hash
197
+
198
+ finalBundleTrytes << returnedTrytes
199
+ rescue => e
200
+ return sendData(false, e.message, &callback)
201
+ end
202
+ end
203
+
204
+ sendData(true, finalBundleTrytes, &callback)
205
+ end
206
+ end
207
+
208
+ def interruptAttachingToTangle(&callback)
209
+ sendCommand(@commands.interruptAttachingToTangle, &callback)
210
+ end
211
+
212
+ def broadcastTransactions(trytes, &callback)
213
+ if !@validator.isArrayOfAttachedTrytes(trytes)
214
+ return sendData(false, "Invalid attached Trytes provided", &callback)
215
+ end
216
+
217
+ sendCommand(@commands.broadcastTransactions(trytes), &callback)
218
+ end
219
+
220
+ def storeTransactions(trytes, &callback)
221
+ if !@validator.isArrayOfAttachedTrytes(trytes)
222
+ return sendData(false, "Invalid attached Trytes provided", &callback)
223
+ end
224
+
225
+ sendCommand(@commands.storeTransactions(trytes), &callback)
226
+ end
227
+
228
+ def checkConsistency(tails, &callback)
229
+ if !@validator.isArrayOfHashes(tails)
230
+ return sendData(false, "Invalid tails provided", &callback)
231
+ end
232
+
233
+ sendCommand(@commands.checkConsistency(tails), &callback)
234
+ end
235
+
236
+ def wereAddressesSpentFrom(addresses, &callback)
237
+ if !@validator.isArrayOfHashes(addresses)
238
+ return sendData(false, "Invalid Trytes provided", &callback)
239
+ end
240
+
241
+ command = @commands.wereAddressesSpentFrom(addresses.map{|address| @utils.noChecksum(address)})
242
+ sendBatchedCommand(command, &callback)
243
+ end
244
+
245
+ private
246
+ def sendData(status, data, &callback)
247
+ callback ? callback.call(status, data) : [status, data]
248
+ end
249
+ end
250
+ end
251
+ end