banano 0.1.0

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.
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'faraday'
4
+ require 'faraday_middleware'
5
+
6
+ module Banano
7
+ class Client
8
+ LOCAL_ENDPOINT = 'http://localhost:7072'
9
+ DEFAULT_TIMEOUT = 30
10
+
11
+ attr_accessor :uri, :timeout
12
+
13
+ def initialize(uri: LOCAL_ENDPOINT, timeout: DEFAULT_TIMEOUT)
14
+ @conn = Faraday.new(uri) do |builder|
15
+ builder.adapter Faraday.default_adapter
16
+ builder.request :url_encoded
17
+ builder.options[:open_timeout] = 5
18
+ builder.options[:timeout] = timeout
19
+ builder.headers['Content-Type'] = 'application/json'
20
+ builder.headers['User-Agent'] = 'Banano RPC Client'
21
+ builder.response :json, content_type: 'application/json'
22
+ end
23
+ end
24
+
25
+ def rpc_call(action:, params: {})
26
+ data = {action: action}.merge(params)
27
+ response = @conn.post do |req|
28
+ req.body = JSON.dump(data)
29
+ end
30
+ Util.symbolize_keys(response.body)
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Banano
4
+ # Standard error for Banano
5
+ class Error < StandardError
6
+ end
7
+ end
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Banano
4
+ class Key
5
+ def initialize(node:, key: nil)
6
+ @node = node
7
+ @key = key
8
+ end
9
+
10
+ def generate(seed: nil, index: nil)
11
+ if seed.nil? && index.nil?
12
+ rpc(action: :key_create)
13
+ elsif !seed.nil? && !index.nil?
14
+ rpc(action: :deterministic_key, params: {seed: seed, index: index})
15
+ else
16
+ raise ArgumentError, "Method must be called with either seed AND index params"
17
+ end
18
+ end
19
+
20
+ # Derive public key and account from private key
21
+ def expand
22
+ return {} if @key.nil?
23
+
24
+ rpc(action: :key_expand, params: {key: @key})
25
+ end
26
+
27
+ def id
28
+ @key
29
+ end
30
+
31
+ def info
32
+ key_required!
33
+ rpc(action: :key_expand)
34
+ end
35
+
36
+ private
37
+
38
+ def rpc(action:, params: {})
39
+ p = @key.nil? ? {} : {key: @key}
40
+ @node.rpc(action: action, params: p.merge(params))
41
+ end
42
+
43
+ def key_required!
44
+ raise ArgumentError, "Key must be present" if @key.nil?
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,115 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Banano
4
+ class Node
5
+ attr_reader :uri, :timeout
6
+
7
+ def initialize(uri: Client::LOCAL_ENDPOINT, timeout: Client::DEFAULT_TIMEOUT)
8
+ @client = Client.new(uri: uri, timeout: timeout)
9
+ end
10
+
11
+ def rpc(action:, params: {})
12
+ @client.rpc_call(action: action, params: params)
13
+ end
14
+
15
+ # The number of accounts in the nano ledger--essentially all
16
+ # accounts with _open_ blocks. An _open_ block
17
+ # is the type of block written to the nano ledger when an account
18
+ # receives its first payment (see {Nanook::WalletAccount#receive}). All accounts
19
+ # that respond +true+ to {Nanook::Account#exists?} have open blocks in the ledger.
20
+ #
21
+ # @return [Integer] number of accounts with _open_ blocks.
22
+ def account_count
23
+ rpc(action: :frontier_count)[:count]
24
+ end
25
+ alias frontier_count account_count
26
+
27
+ # The count of all blocks downloaded to the node, and
28
+ # blocks still to be synchronized by the node.
29
+ #
30
+ # @return [Hash{Symbol=>Integer}] number of blocks and unchecked
31
+ # synchronizing blocks
32
+ def block_count
33
+ rpc(action: :block_count)
34
+ end
35
+
36
+ # The count of all known blocks by their type.
37
+ #
38
+ # @return [Hash{Symbol=>Integer}] number of blocks by type
39
+ def block_count_by_type
40
+ rpc(action: :block_count_type)
41
+ end
42
+ alias block_count_type block_count_by_type
43
+
44
+ # TODO: add bootstrap methods
45
+
46
+ # @return [Hash{Symbol=>String}] information about the node peers
47
+ def peers
48
+ rpc(action: :peers)[:peers]
49
+ end
50
+
51
+ # All representatives and their voting weight.
52
+ #
53
+ # @param raw [Boolean] if true return raw balances, else banano units
54
+ # @return [Hash{Symbol=>Integer}] known representatives and their voting weight
55
+ def representatives(raw = true)
56
+ response = rpc(action: :representatives)[:representatives]
57
+ return response if raw == true
58
+
59
+ r = response.map do |address, balance|
60
+ [address.to_s, Banano::Unit.raw_to_ban(balance).to_f]
61
+ end
62
+ Hash[r]
63
+ end
64
+
65
+ # All online representatives that have voted recently. Note, due to the
66
+ # design of the nano RPC, this method cannot return the voting weight
67
+ # of the representatives.
68
+ #
69
+ # ==== Example:
70
+ #
71
+ # node.representatives_online # => ["ban_111...", "ban_311..."]
72
+ #
73
+ # @return [Array<String>] array of representative account ids
74
+ def representatives_online
75
+ rpc(action: :representatives_online)[:representatives]
76
+ end
77
+ alias reps_online representatives_online
78
+
79
+ # @param limit [Integer] number of synchronizing blocks to return
80
+ # @return [Hash{Symbol=>String}] information about the synchronizing blocks for this node
81
+ def synchronizing_blocks(limit: 1000)
82
+ response = rpc(action: :unchecked, params: {count: limit})[:blocks]
83
+ # response = response.map do |block, info|
84
+ # [block, JSON.parse(info).to_symbolized_hash]
85
+ # end
86
+ # Hash[response.sort].to_symbolized_hash
87
+ response
88
+ end
89
+ alias unchecked synchronizing_blocks
90
+
91
+ # The percentage completeness of the synchronization process for
92
+ # your node as it downloads the nano ledger. Note, it's normal for
93
+ # your progress to not ever reach 100. The closer to 100, the more
94
+ # complete your node's data is, and so the query methods in this class
95
+ # become more reliable.
96
+ #
97
+ # @return [Float] the percentage completeness of the synchronization
98
+ # process for your node
99
+ def sync_progress
100
+ response = block_count
101
+
102
+ count = response[:count].to_i
103
+ unchecked = response[:unchecked].to_i
104
+ total = count + unchecked
105
+
106
+ count.to_f * 100 / total.to_f
107
+ end
108
+
109
+ # @return [Hash{Symbol=>Integer|String}] version information for this node
110
+ def version
111
+ rpc(action: :version)
112
+ end
113
+ alias info version
114
+ end
115
+ end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bigdecimal'
4
+
5
+ module Banano
6
+ class Unit
7
+ # Constant used to convert back and forth between raw and banano
8
+ STEP = BigDecimal(10)**29
9
+ TOTAL = (BigDecimal(2)**128 - 1).to_i
10
+
11
+ # Converts an amount of banano to an amount of raw.
12
+ #
13
+ # @param banano [Float|Integer] amount in banano
14
+ # @return [Integer] amount in raw
15
+ def self.ban_to_raw(banano)
16
+ return 0 unless banano.is_a?(Numeric) && banano > 0
17
+
18
+ result = (banano * STEP).to_i
19
+ return 0 if result > TOTAL
20
+
21
+ result
22
+ end
23
+
24
+ # Converts an amount of raw to an amount of banano
25
+ #
26
+ # @param raw [BigDecimal|String] amount in raw
27
+ # @return [Float|Integer] amount in banano
28
+ def self.raw_to_ban(raw)
29
+ return 0 unless raw.is_a?(BigDecimal) || raw.is_a?(String)
30
+
31
+ begin
32
+ value = raw.is_a?(String) ? BigDecimal(raw) : raw
33
+ return 0 if value < 1.0 || value > TOTAL
34
+
35
+ value / STEP
36
+ rescue ArgumentError
37
+ 0
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Banano
4
+ class Util
5
+ class << self
6
+ def symbolize_keys(hash)
7
+ return {} if hash.empty?
8
+
9
+ converted = hash.is_a?(String) ? JSON.parse(hash) : hash
10
+ converted.inject({}) do |result, (key, value)|
11
+ new_key = case key
12
+ when String then key.to_sym
13
+ else key
14
+ end
15
+ new_value = case value
16
+ when Hash then symbolize_keys(value)
17
+ else value
18
+ end
19
+ result[new_key] = new_value
20
+ result
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Banano
4
+ VERSION = '0.1.0'
5
+ end
@@ -0,0 +1,430 @@
1
+ # frozen_string_literal: true
2
+
3
+ # The <tt>Banano::Wallet</tt> class lets you manage your banano wallets,
4
+ # as well as some account-specific things like making and receiving payments.
5
+ #
6
+ # === Wallet seeds vs ids
7
+ #
8
+ # Your wallets each have an id as well as a seed. Both are 32-byte uppercase hex
9
+ # strings that look like this:
10
+ #
11
+ # 000D1BAEC8EC208142C99059B393051BAC8380F9B5A2E6B2489A277D81789F3F
12
+ #
13
+ # This class uses wallet _ids_ to identify your wallet. A wallet id only
14
+ # exists locally on the banano node that it was created on. The person
15
+ # who knows this id can only perform all read and write actions against
16
+ # the wallet and all accounts inside the wallet from the same banano node
17
+ # that it was created on. This makes wallet ids fairly safe to use as a
18
+ # person needs to know your wallet id as well as have access to run
19
+ # RPC commands against your banano node to be able to control your accounts.
20
+ #
21
+ # A _seed_ on the other hand can be used to link any wallet to another
22
+ # wallet's accounts, from anywhere in the banano network. This happens
23
+ # by setting a wallet's seed to be the same as a previous wallet's seed.
24
+ # When a wallet has the same seed as another wallet, any accounts
25
+ # created in the second wallet will be the same accounts as those that were
26
+ # created in the previous wallet, and the new wallet's owner will
27
+ # also gain ownership of the previous wallet's accounts. Note, that the
28
+ # two wallets will have different ids, but the same seed.
29
+ #
30
+
31
+ module Banano
32
+ class Wallet
33
+ attr_reader :node
34
+
35
+ def initialize(node:, wallet: nil)
36
+ @node = node
37
+ @wallet = wallet
38
+ end
39
+
40
+ # Changes a wallet's seed.
41
+ #
42
+ # It's recommended to only change the seed of a wallet that contains
43
+ # no accounts.
44
+ #
45
+ # ==== Example:
46
+ #
47
+ # wallet.change_seed("000D1BA...") # => true
48
+ #
49
+ # @param seed [String] the seed to change to.
50
+ # @return [Boolean] indicating whether the change was successful.
51
+ def change_seed(seed)
52
+ wallet_required!
53
+ rpc(action: :wallet_change_seed, params: {seed: seed}).key?(:success)
54
+ end
55
+
56
+ # Returns the given account in the wallet as a {Banano::WalletAccount} instance
57
+ # to let you start working with it.
58
+ #
59
+ # Call with no +account+ argument if you wish to create a new account
60
+ # in the wallet, like this:
61
+ #
62
+ # wallet.account.create # => Banano::WalletAccount
63
+ #
64
+ # See {Banano::WalletAccount} for all the methods you can call on the
65
+ # account object returned.
66
+ #
67
+ # ==== Examples:
68
+ #
69
+ # wallet.account("nano_...") # => Banano::WalletAccount
70
+ # wallet.account.create # => Banano::WalletAccount
71
+ #
72
+ # @param [String] account optional String of an account (starting with
73
+ # <tt>"xrb..."</tt>) to start working with. Must be an account within
74
+ # the wallet. When no account is given, the instance returned only
75
+ # allows you to call +create+ on it, to create a new account.
76
+ # @raise [ArgumentError] if the wallet does no contain the account
77
+ # @return [Banano::WalletAccount]
78
+ def account(account = nil)
79
+ Banano::WalletAccount.new(node: @node, wallet: @wallet, account: account)
80
+ end
81
+
82
+ # Array of {Banano::WalletAccount} instances of accounts in the wallet.
83
+ #
84
+ # ==== Example:
85
+ #
86
+ # wallet.accounts # => [Banano::WalletAccount, Banano::WalletAccount...]
87
+ #
88
+ # @return [Array<Banano::WalletAccount>] all accounts in the wallet
89
+ def accounts
90
+ wallet_required!
91
+ rpc(action: :account_list)[:accounts]
92
+ end
93
+
94
+ # Will return +true+ if the account exists in the wallet.
95
+ #
96
+ # ==== Example:
97
+ # wallet.contains?("ban_1...") # => true
98
+ #
99
+ # @param account [String] id (will start with <tt>"ban_..."</tt>)
100
+ # @return [Boolean] indicating if the wallet contains the given account
101
+ # TODO: account address validation - Maybe Banano::Address ....
102
+ def contains?(account)
103
+ wallet_required!
104
+ response = rpc(action: :wallet_contains, params: {account: account})
105
+ !response.empty? && response[:exists] == '1'
106
+ end
107
+
108
+ # Creates a new wallet.
109
+ #
110
+ # The wallet will be created only on this node. It's important that
111
+ # if you intend to add funds to accounts in this wallet that you
112
+ # backup the wallet *seed* in order to restore the wallet in future.
113
+ #
114
+ # ==== Example:
115
+ # Banano::Wallet.new.create # => Banano::Wallet
116
+ #
117
+ # @return [Banano::Wallet]
118
+ def create
119
+ @wallet = rpc(action: :wallet_create)[:wallet]
120
+ self
121
+ end
122
+
123
+ # Destroys the wallet.
124
+ #
125
+ # ==== Example:
126
+ #
127
+ # wallet.destroy # => true
128
+ #
129
+ # @return [Boolean] indicating success of the action
130
+ def destroy
131
+ wallet_required!
132
+ rpc(action: :wallet_destroy)
133
+ @wallet = nil
134
+ true
135
+ end
136
+
137
+ # Generates a String containing a JSON representation of your wallet.
138
+ #
139
+ def export
140
+ wallet_required!
141
+ rpc(action: :wallet_export)[:json]
142
+ end
143
+
144
+ # The default representative account id for the wallet. This is the
145
+ # representative that all new accounts created in this wallet will have.
146
+ #
147
+ # Changing the default representative for a wallet does not change
148
+ # the representatives for any accounts that have been created.
149
+ #
150
+ # ==== Example:
151
+ #
152
+ # wallet.default_representative # => "ban_3pc..."
153
+ #
154
+ # @return [String] Representative account of the account
155
+ def default_representative
156
+ rpc(action: :wallet_representative)[:representative]
157
+ end
158
+ alias representative default_representative
159
+
160
+ # Sets the default representative for the wallet. A wallet's default
161
+ # representative is the representative all new accounts created in
162
+ # the wallet will have. Changing the default representative for a
163
+ # wallet does not change the representatives for existing accounts
164
+ # in the wallet.
165
+ #
166
+ # ==== Example:
167
+ #
168
+ # wallet.change_default_representative("ban_...") # => "ban_..."
169
+ #
170
+ # @param [String] representative the id of the representative account
171
+ # to set as this account's representative
172
+ # @return [String] the representative account id
173
+ # @raise [ArgumentError] if the representative account does not exist
174
+ # @raise [Banano::Error] if setting the representative fails
175
+ def change_default_representative(representative)
176
+ unless Banano::Account.new(node: @node, address: representative).exists?
177
+ raise ArgumentError, "Representative account does not exist: #{representative}"
178
+ end
179
+
180
+ if rpc(action: :wallet_representative_set,
181
+ params: {representative: representative})[:set] == '1'
182
+ representative
183
+ else
184
+ raise Banano::Error, "Setting the representative failed"
185
+ end
186
+ end
187
+ alias change_representative change_default_representative
188
+
189
+ # @return [String] the wallet id
190
+ def id
191
+ @wallet
192
+ end
193
+
194
+ # Information about this wallet and all of its accounts.
195
+ #
196
+ # ==== Examples:
197
+ #
198
+ # wallet.info
199
+ #
200
+ # @param raw [Boolean] if true return raw, else return ban units
201
+ # @return [Hash{Symbol=>String|Array<Hash{Symbol=>String|Integer|Float}>}]
202
+ # information about the wallet.
203
+ def info
204
+ wallet_required!
205
+ rpc(action: :wallet_info)
206
+ end
207
+
208
+ # Locks the wallet. A locked wallet cannot pocket pending transactions or make payments.
209
+ #
210
+ # ==== Example:
211
+ #
212
+ # wallet.lock #=> true
213
+ #
214
+ # @return [Boolean] indicates if the wallet was successfully locked
215
+ def lock
216
+ wallet_required!
217
+ response = rpc(action: :wallet_lock)
218
+ !response.empty? && response[:locked] == '1'
219
+ end
220
+
221
+ # Returns +true+ if the wallet is locked.
222
+ #
223
+ # ==== Example:
224
+ #
225
+ # wallet.locked? #=> false
226
+ #
227
+ # @return [Boolean] indicates if the wallet is locked
228
+ def locked?
229
+ wallet_required!
230
+ response = rpc(action: :wallet_locked)
231
+ !response.empty? && response[:locked] != '0'
232
+ end
233
+
234
+ # Unlocks a previously locked wallet.
235
+ #
236
+ # ==== Example:
237
+ #
238
+ # wallet.unlock("new_pass") #=> true
239
+ #
240
+ # @return [Boolean] indicates if the unlocking action was successful
241
+ def unlock(password)
242
+ wallet_required!
243
+ rpc(action: :password_enter, params: {password: password})[:valid] == '1'
244
+ end
245
+
246
+ # Changes the password for a wallet.
247
+ #
248
+ # ==== Example:
249
+ #
250
+ # wallet.change_password("new_pass") #=> true
251
+ # @return [Boolean] indicates if the action was successful
252
+ def change_password(password)
253
+ wallet_required!
254
+ rpc(action: :password_change, params: {password: password})[:changed] == '1'
255
+ end
256
+
257
+ # Balance of all accounts in the wallet, optionally breaking the balances down by account.
258
+ #
259
+ # ==== Examples:
260
+ # wallet.balance
261
+ #
262
+ # Example response:
263
+ #
264
+ # {
265
+ # "balance"=>5,
266
+ # "pending"=>0.001
267
+ # }
268
+ #
269
+ # @param [Boolean] account_break_down (default is +false+). When +true+
270
+ # the response will contain balances per account.
271
+ # @param raw [Boolean] raw or banano units
272
+ #
273
+ # @return [Hash{Symbol=>Integer|Float|Hash}]
274
+ def balance(account_break_down: false, raw: true)
275
+ wallet_required!
276
+
277
+ if account_break_down
278
+ response = rpc(action: :wallet_balances)[:balances].tap do |r|
279
+ unless raw == true
280
+ r.each do |account, _|
281
+ r[account][:balance] = Banano::Unit.raw_to_ban(r[account][:balance]).to_f
282
+ r[account][:pending] = Banano::Unit.raw_to_ban(r[account][:pending]).to_f
283
+ end
284
+ end
285
+ end
286
+ return response.collect {|k, v| [k.to_s, v] }.to_h
287
+ end
288
+
289
+ rpc(action: :wallet_balance_total).tap do |r|
290
+ unless raw == true
291
+ r[:balance] = Banano::Unit.raw_to_ban(r[:balance]).to_f
292
+ r[:pending] = Banano::Unit.raw_to_ban(r[:pending]).to_f
293
+ end
294
+ end
295
+ end
296
+
297
+ # Makes a payment from an account in your wallet to another account
298
+ # on the nano network.
299
+ #
300
+ # Note, there may be a delay in receiving a response due to Proof of
301
+ # Work being done. From the {Nano RPC}[https://docs.nano.org/commands/rpc-protocol/#send]:
302
+ #
303
+ # <i>Proof of Work is precomputed for one transaction in the
304
+ # background. If it has been a while since your last transaction it
305
+ # will send instantly, the next one will need to wait for Proof of
306
+ # Work to be generated.</i>
307
+ #
308
+ # @param from [String] account id of an account in your wallet
309
+ # @param to (see Banano::WalletAccount#pay)
310
+ # @param amount (see Banano::WalletAccount#pay)
311
+ # @param raw [Boolean] raw or banano units
312
+ # @params id (see Banano::WalletAccount#pay)
313
+ #
314
+ # @return (see Banano::WalletAccount#pay)
315
+ # @raise [Banano::Error] if unsuccessful
316
+ def pay(from:, to:, amount:, raw: true, id:)
317
+ wallet_required!
318
+ validate_wallet_contains_account!(from)
319
+ # account(from) will return Banano::WalletAccount
320
+ account(from).pay(to: to, amount: amount, raw: raw, id: id)
321
+ end
322
+
323
+ # Information about pending blocks (payments) that are waiting
324
+ # to be received by accounts in this wallet.
325
+ #
326
+ # See also the {#receive} method of this class for how to receive a pending payment.
327
+ #
328
+ # @param limit [Integer] number of accounts with pending payments to return (default is 1000)
329
+ # @param detailed [Boolean] return complex Hash of pending block info (default is +false+)
330
+ # @param raw [Boolean] raw or banano units
331
+ #
332
+ # ==== Examples:
333
+ #
334
+ # wallet.pending
335
+ #
336
+ def pending(limit: 1000, detailed: false, raw: true)
337
+ wallet_required!
338
+ params = {count: limit}
339
+ params[:source] = true if detailed
340
+
341
+ response = rpc(action: :wallet_pending, params: params)[:blocks]
342
+ return response unless detailed && !response.empty?
343
+
344
+ # Map the RPC response, which is:
345
+ # account=>block=>[amount|source] into
346
+ # account=>[block|amount|source]
347
+ response.map do |account, data|
348
+ new_data = data.map do |block, amount_and_source|
349
+ d = amount_and_source.merge(block: block.to_s)
350
+ d[:amount] = Banano::Unit.raw_to_ban(d[:amount]) unless raw == true
351
+ d
352
+ end
353
+
354
+ [account, new_data]
355
+ end
356
+ end
357
+
358
+ # Receives a pending payment into an account in the wallet.
359
+ #
360
+ # When called with no +block+ argument, the latest pending payment
361
+ # for the account will be received.
362
+ #
363
+ # Returns a <i>receive</i> block hash id if a receive was successful,
364
+ # or +false+ if there were no pending payments to receive.
365
+ #
366
+ # You can receive a specific pending block if you know it by
367
+ # passing the block has in as an argument.
368
+ # ==== Examples:
369
+ #
370
+ # wallet.receive(into: "ban_..") # => "9AE2311..."
371
+ # wallet.receive("718CC21...", into: "ban_..") # => "9AE2311..."
372
+ #
373
+ # @param block (see Banano::WalletAccount#receive)
374
+ # @param into [String] account id of account in your wallet to receive the
375
+ # payment into
376
+ #
377
+ # @return (see Banano::WalletAccount#receive)
378
+ def receive(block: nil, into:)
379
+ wallet_required!
380
+ validate_wallet_contains_account!(into)
381
+ # account(into) will return Banano::WalletAccount
382
+ account(into).receive(block)
383
+ end
384
+
385
+ # Restores a previously created wallet by its seed.
386
+ # A new wallet will be created on your node (with a new wallet id)
387
+ # and will have its seed set to the given seed.
388
+ #
389
+ # ==== Example:
390
+ #
391
+ # Banano::Protocol.new.wallet.restore(seed) # => Nanook::Wallet
392
+ #
393
+ # @param seed [String] the wallet seed to restore.
394
+ # @param accounts [Integer] optionally restore the given number of accounts for the wallet.
395
+ #
396
+ # @return [Banano::Wallet] a new wallet
397
+ # @raise [Banano::Error] if unsuccessful
398
+ def restore(seed:, accounts: 0)
399
+ create
400
+
401
+ raise Nanook::Error, "Unable to set seed for wallet" unless change_seed(seed)
402
+
403
+ account.create(accounts) if accounts > 0
404
+ self
405
+ end
406
+
407
+ private
408
+
409
+ def rpc(action:, params: {})
410
+ p = @wallet.nil? ? {} : {wallet: @wallet}
411
+ @node.rpc(action: action, params: p.merge(params))
412
+ end
413
+
414
+ def wallet_required!
415
+ raise ArgumentError, "Wallet must be present" if @wallet.nil?
416
+ end
417
+
418
+ def validate_wallet_contains_account!(account)
419
+ @known_valid_accounts ||= []
420
+ return true if @known_valid_accounts.include?(account)
421
+
422
+ if contains?(account)
423
+ @known_valid_accounts << account
424
+ else
425
+ raise ArgumentError,
426
+ "Account does not exist in wallet. Account: #{account}, wallet: #{@wallet}"
427
+ end
428
+ end
429
+ end
430
+ end