nanook 2.5.0 → 4.0.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.
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  class Nanook
2
- VERSION = "2.5.0"
4
+ VERSION = '4.0.0'
3
5
  end
data/lib/nanook/wallet.rb CHANGED
@@ -1,7 +1,10 @@
1
- class Nanook
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'util'
2
4
 
3
- # The <tt>Nanook::Wallet</tt> class lets you manage your nano wallets,
4
- # as well as some account-specific things like making and receiving payments.
5
+ class Nanook
6
+ # The <tt>Nanook::Wallet</tt> class lets you manage your nano wallets.
7
+ # Your node will need the <tt>enable_control</tt> setting enabled.
5
8
  #
6
9
  # === Wallet seeds vs ids
7
10
  #
@@ -33,8 +36,7 @@ class Nanook
33
36
  # want to restore the wallet anywhere else on the nano network besides
34
37
  # the node you originally created it on. The nano command line interface
35
38
  # (CLI) is the only method for discovering a wallet's seed. See the
36
- # {https://developers.nano.org/docs/command-line-interface/#wallet_decrypt_unsafe
37
- # --wallet_decrypt_unsafe CLI command}.
39
+ # {https://docs.nano.org/commands/command-line-interface/#-wallet_decrypt_unsafe-walletwallet-passwordpassword}.
38
40
  #
39
41
  # === Initializing
40
42
  #
@@ -48,10 +50,32 @@ class Nanook
48
50
  # rpc_conn = Nanook::Rpc.new
49
51
  # wallet = Nanook::Wallet.new(rpc_conn, wallet_id)
50
52
  class Wallet
53
+ include Nanook::Util
51
54
 
52
- def initialize(rpc, wallet)
55
+ def initialize(rpc, wallet = nil)
53
56
  @rpc = rpc
54
- @wallet = wallet
57
+ @wallet = wallet.to_s if wallet
58
+ end
59
+
60
+ # @return [String] the wallet id
61
+ def id
62
+ @wallet
63
+ end
64
+
65
+ # @param other [Nanook::Wallet] wallet to compare
66
+ # @return [Boolean] true if wallets are equal
67
+ def ==(other)
68
+ other.class == self.class &&
69
+ other.id == id
70
+ end
71
+ alias eql? ==
72
+
73
+ # The hash value is used along with #eql? by the Hash class to determine if two objects
74
+ # reference the same hash key.
75
+ #
76
+ # @return [Integer]
77
+ def hash
78
+ id.hash
55
79
  end
56
80
 
57
81
  # Returns the given account in the wallet as a {Nanook::WalletAccount} instance
@@ -70,14 +94,17 @@ class Nanook
70
94
  # wallet.account("nano_...") # => Nanook::WalletAccount
71
95
  # wallet.account.create # => Nanook::WalletAccount
72
96
  #
73
- # @param [String] account optional String of an account (starting with
97
+ # @param account [String] optional String of an account (starting with
74
98
  # <tt>"xrb..."</tt>) to start working with. Must be an account within
75
99
  # the wallet. When no account is given, the instance returned only
76
100
  # allows you to call +create+ on it, to create a new account.
77
- # @raise [ArgumentError] if the wallet does no contain the account
101
+ # @raise [ArgumentError] if the wallet does not contain the account
78
102
  # @return [Nanook::WalletAccount]
79
- def account(account=nil)
80
- Nanook::WalletAccount.new(@rpc, @wallet, account)
103
+ def account(account = nil)
104
+ check_wallet_required!
105
+
106
+ # We `allow_blank` in order to support `WalletAccount#create`.
107
+ as_wallet_account(account, allow_blank: true)
81
108
  end
82
109
 
83
110
  # Array of {Nanook::WalletAccount} instances of accounts in the wallet.
@@ -91,13 +118,33 @@ class Nanook
91
118
  #
92
119
  # @return [Array<Nanook::WalletAccount>] all accounts in the wallet
93
120
  def accounts
94
- wallet_required!
95
- response = rpc(:account_list)[:accounts]
96
- Nanook::Util.coerce_empty_string_to_type(response, Array).map do |account|
97
- Nanook::WalletAccount.new(@rpc, @wallet, account)
121
+ rpc(:account_list, _access: :accounts, _coerce: Array).map do |account|
122
+ as_wallet_account(account)
98
123
  end
99
124
  end
100
125
 
126
+ # Move accounts from another {Nanook::Wallet} on the node to this {Nanook::Wallet}.
127
+ #
128
+ # ==== Example:
129
+ #
130
+ # wallet.move_accounts("0023200...", ["nano_3e3j5...", "nano_5f2a1..."]) # => true
131
+ #
132
+ # @return [Boolean] true when the move was successful
133
+ def move_accounts(wallet, accounts)
134
+ rpc(:account_move, source: wallet, accounts: accounts, _access: :moved) == 1
135
+ end
136
+
137
+ # Remove an {Nanook::Account} from this {Nanook::Wallet}.
138
+ #
139
+ # ==== Example:
140
+ #
141
+ # wallet.remove_account("nano_3e3j5...") # => true
142
+ #
143
+ # @return [Boolean] true when the remove was successful
144
+ def remove_account(account)
145
+ rpc(:account_remove, account: account, _access: :removed) == 1
146
+ end
147
+
101
148
  # Balance of all accounts in the wallet, optionally breaking the balances down by account.
102
149
  #
103
150
  # ==== Examples:
@@ -138,51 +185,50 @@ class Nanook
138
185
  # },
139
186
  # }
140
187
  #
141
- # @param [Boolean] account_break_down (default is +false+). When +true+
188
+ # @param account_break_down [Boolean] (default is +false+). When +true+
142
189
  # the response will contain balances per account.
143
190
  # @param unit (see Nanook::Account#balance)
144
191
  #
145
192
  # @return [Hash{Symbol=>Integer|Float|Hash}]
193
+ # @raise [Nanook::NanoUnitError] if `unit` is invalid
146
194
  def balance(account_break_down: false, unit: Nanook.default_unit)
147
- wallet_required!
148
-
149
- unless Nanook::UNITS.include?(unit)
150
- raise ArgumentError.new("Unsupported unit: #{unit}")
151
- end
195
+ validate_unit!(unit)
152
196
 
153
197
  if account_break_down
154
- return Nanook::Util.coerce_empty_string_to_type(rpc(:wallet_balances)[:balances], Hash).tap do |r|
198
+ return rpc(:wallet_balances, _access: :balances, _coerce: Hash).tap do |r|
155
199
  if unit == :nano
156
- r.each do |account, balances|
157
- r[account][:balance] = Nanook::Util.raw_to_NANO(r[account][:balance])
158
- r[account][:pending] = Nanook::Util.raw_to_NANO(r[account][:pending])
200
+ r.each do |account, _balances|
201
+ r[account][:balance] = raw_to_NANO(r[account][:balance])
202
+ r[account][:pending] = raw_to_NANO(r[account][:pending])
159
203
  end
160
204
  end
161
205
  end
162
206
  end
163
207
 
164
- rpc(:wallet_balance_total).tap do |r|
165
- if unit == :nano
166
- r[:balance] = Nanook::Util.raw_to_NANO(r[:balance])
167
- r[:pending] = Nanook::Util.raw_to_NANO(r[:pending])
168
- end
169
- end
208
+ response = rpc(:wallet_info, _coerce: Hash).slice(:balance, :pending)
209
+ return response unless unit == :nano
210
+
211
+ {
212
+ balance: raw_to_NANO(response[:balance]),
213
+ pending: raw_to_NANO(response[:pending])
214
+ }
170
215
  end
171
216
 
172
217
  # Changes a wallet's seed.
173
218
  #
174
219
  # It's recommended to only change the seed of a wallet that contains
175
- # no accounts.
220
+ # no accounts. This will clear all deterministic accounts in the wallet.
221
+ # To restore accounts after changing the seed, see Nanook::WalletAccount#create.
176
222
  #
177
223
  # ==== Example:
178
224
  #
179
225
  # wallet.change_seed("000D1BA...") # => true
226
+ # wallet.account.create(5) # Restores first 5 accounts for wallet with new seed
180
227
  #
181
228
  # @param seed [String] the seed to change to.
182
229
  # @return [Boolean] indicating whether the change was successful.
183
230
  def change_seed(seed)
184
- wallet_required!
185
- rpc(:wallet_change_seed, seed: seed).has_key?(:success)
231
+ rpc(:wallet_change_seed, seed: seed).key?(:success)
186
232
  end
187
233
 
188
234
  # Creates a new wallet.
@@ -200,7 +246,8 @@ class Nanook
200
246
  #
201
247
  # @return [Nanook::Wallet]
202
248
  def create
203
- @wallet = rpc(:wallet_create)[:wallet]
249
+ skip_wallet_required!
250
+ @wallet = rpc(:wallet_create, _access: :wallet)
204
251
  self
205
252
  end
206
253
 
@@ -212,19 +259,33 @@ class Nanook
212
259
  #
213
260
  # @return [Boolean] indicating success of the action
214
261
  def destroy
215
- wallet_required!
216
- rpc(:wallet_destroy)
217
- true
262
+ rpc(:wallet_destroy, _access: :destroyed) == 1
218
263
  end
219
264
 
220
265
  # Generates a String containing a JSON representation of your wallet.
221
266
  #
222
267
  # ==== Example:
223
268
  #
224
- # wallet.export # => "{\n \"0000000000000000000000000000000000000000000000000000000000000000\": \"0000000000000000000000000000000000000000000000000000000000000003\",\n \"0000000000000000000000000000000000000000000000000000000000000001\": \"C3A176FC3B90113277BFC91F55128FC9A1F1B6166A73E7446927CFFCA4C2C9D9\",\n \"0000000000000000000000000000000000000000000000000000000000000002\": \"3E58EC805B99C52B4715598BD332C234A1FBF1780577137E18F53B9B7F85F04B\",\n \"0000000000000000000000000000000000000000000000000000000000000003\": \"5FF8021122F3DEE0E4EC4241D35A3F41DEF63CCF6ADA66AF235DE857718498CD\",\n \"0000000000000000000000000000000000000000000000000000000000000004\": \"A30E0A32ED41C8607AA9212843392E853FCBCB4E7CB194E35C94F07F91DE59EF\",\n \"0000000000000000000000000000000000000000000000000000000000000005\": \"E707002E84143AA5F030A6DB8DD0C0480F2FFA75AB1FFD657EC22B5AA8E395D5\",\n \"0000000000000000000000000000000000000000000000000000000000000006\": \"0000000000000000000000000000000000000000000000000000000000000001\",\n \"8646C0423160DEAEAA64034F9C6858F7A5C8A329E73E825A5B16814F6CCAFFE3\": \"0000000000000000000000000000000000000000000000000000000100000000\"\n}\n"
269
+ # wallet.export
270
+ # # => "{\n \"0000000000000000000000000000000000000000000000000000000000000000\": \"0000000000000000000000000000000000000000000000000000000000000003\",\n \"0000000000000000000000000000000000000000000000000000000000000001\": \"C3A176FC3B90113277BFC91F55128FC9A1F1B6166A73E7446927CFFCA4C2C9D9\",\n \"0000000000000000000000000000000000000000000000000000000000000002\": \"3E58EC805B99C52B4715598BD332C234A1FBF1780577137E18F53B9B7F85F04B\",\n \"0000000000000000000000000000000000000000000000000000000000000003\": \"5FF8021122F3DEE0E4EC4241D35A3F41DEF63CCF6ADA66AF235DE857718498CD\",\n \"0000000000000000000000000000000000000000000000000000000000000004\": \"A30E0A32ED41C8607AA9212843392E853FCBCB4E7CB194E35C94F07F91DE59EF\",\n \"0000000000000000000000000000000000000000000000000000000000000005\": \"E707002E84143AA5F030A6DB8DD0C0480F2FFA75AB1FFD657EC22B5AA8E395D5\",\n \"0000000000000000000000000000000000000000000000000000000000000006\": \"0000000000000000000000000000000000000000000000000000000000000001\",\n \"8646C0423160DEAEAA64034F9C6858F7A5C8A329E73E825A5B16814F6CCAFFE3\": \"0000000000000000000000000000000000000000000000000000000100000000\"\n}\n"
271
+ #
272
+ # @return [String]
225
273
  def export
226
- wallet_required!
227
- rpc(:wallet_export)[:json]
274
+ rpc(:wallet_export, _access: :json)
275
+ end
276
+
277
+ # Returns true if wallet exists on the node.
278
+ #
279
+ # ==== Example:
280
+ #
281
+ # wallet.exists? # => true
282
+ #
283
+ # @return [Boolean] true if wallet exists on the node
284
+ def exists?
285
+ export
286
+ true
287
+ rescue Nanook::NodeRpcError
288
+ false
228
289
  end
229
290
 
230
291
  # Will return +true+ if the account exists in the wallet.
@@ -235,26 +296,20 @@ class Nanook
235
296
  # @param account [String] id (will start with <tt>"nano_..."</tt>)
236
297
  # @return [Boolean] indicating if the wallet contains the given account
237
298
  def contains?(account)
238
- wallet_required!
239
- response = rpc(:wallet_contains, account: account)
240
- !response.empty? && response[:exists] == 1
241
- end
242
-
243
- # @return [String] the wallet id
244
- def id
245
- @wallet
299
+ rpc(:wallet_contains, account: account, _access: :exists) == 1
246
300
  end
247
301
 
248
302
  # @return [String]
249
- def inspect
250
- "#{self.class.name}(id: \"#{id}\", object_id: \"#{"0x00%x" % (object_id << 1)}\")"
303
+ def to_s
304
+ "#{self.class.name}(id: \"#{short_id}\")"
251
305
  end
306
+ alias inspect to_s
252
307
 
253
308
  # Makes a payment from an account in your wallet to another account
254
309
  # on the nano network.
255
310
  #
256
311
  # Note, there may be a delay in receiving a response due to Proof of
257
- # Work being done. From the {Nano RPC}[https://developers.nano.org/docs/rpc#send]:
312
+ # Work being done. From the {Nano RPC}[https://docs.nano.org/commands/rpc-protocol/#send]:
258
313
  #
259
314
  # <i>Proof of Work is precomputed for one transaction in the
260
315
  # background. If it has been a while since your last transaction it
@@ -264,7 +319,8 @@ class Nanook
264
319
  # ==== Examples:
265
320
  #
266
321
  # wallet.pay(from: "nano_...", to: "nano_...", amount: 1.1, id: "myUniqueId123") # => "9AE2311..."
267
- # wallet.pay(from: "nano_...", to: "nano_...", amount: 54000000000000, unit: :raw, id: "myUniqueId123") # => "9AE2311..."
322
+ # wallet.pay(from: "nano_...", to: "nano_...", amount: 54000000000000, unit: :raw, id: "myUniqueId123")
323
+ # # => "9AE2311..."
268
324
  #
269
325
  # @param from [String] account id of an account in your wallet
270
326
  # @param to (see Nanook::WalletAccount#pay)
@@ -273,8 +329,7 @@ class Nanook
273
329
  # @params id (see Nanook::WalletAccount#pay)
274
330
  # @return (see Nanook::WalletAccount#pay)
275
331
  # @raise [Nanook::Error] if unsuccessful
276
- def pay(from:, to:, amount:, unit: Nanook.default_unit, id:)
277
- wallet_required!
332
+ def pay(from:, to:, amount:, id:, unit: Nanook.default_unit)
278
333
  validate_wallet_contains_account!(from)
279
334
  account(from).pay(to: to, amount: amount, unit: unit, id: id)
280
335
  end
@@ -285,6 +340,8 @@ class Nanook
285
340
  # See also the {#receive} method of this class for how to receive a pending payment.
286
341
  #
287
342
  # @param limit [Integer] number of accounts with pending payments to return (default is 1000)
343
+ # @param allow_unconfirmed [Boolean] +false+ by default. When +false+ only returns block which
344
+ # have their confirmation height set or are undergoing confirmation height processing.
288
345
  # @param detailed [Boolean]return a more complex Hash of pending block information (default is +false+)
289
346
  # @param unit (see Nanook::Account#balance)
290
347
  #
@@ -295,12 +352,12 @@ class Nanook
295
352
  # Example response:
296
353
  #
297
354
  # {
298
- # :nano_1111111111111111111111111111111111111111111111111117353trpda=>[
299
- # "142A538F36833D1CC78B94E11C766F75818F8B940771335C6C1B8AB880C5BB1D",
300
- # "718CC2121C3E641059BC1C2CFC45666C99E8AE922F7A807B7D07B62C995D79E2"
355
+ # Nanook::Account=>[
356
+ # Nanook::Block,
357
+ # Nanook::Block"
301
358
  # ],
302
- # :nano_3t6k35gi95xu6tergt6p69ck76ogmitsa8mnijtpxm9fkcm736xtoncuohr3=>[
303
- # "4C1FEEF0BEA7F50BE35489A1233FE002B212DEA554B55B1B470D78BD8F210C74"
359
+ # Nanook::Account=>[
360
+ # Nanook::Block
304
361
  # ]
305
362
  # }
306
363
  #
@@ -311,57 +368,70 @@ class Nanook
311
368
  # Example response:
312
369
  #
313
370
  # {
314
- # :nano_1111111111111111111111111111111111111111111111111117353trpda=>[
371
+ # Nanook::Account=>[
315
372
  # {
316
373
  # :amount=>6.0,
317
- # :source=>"nano_3dcfozsmekr1tr9skf1oa5wbgmxt81qepfdnt7zicq5x3hk65fg4fqj58mbr",
318
- # :block=>:"142A538F36833D1CC78B94E11C766F75818F8B940771335C6C1B8AB880C5BB1D"
374
+ # :source=>Nanook::Account,
375
+ # :block=>Nanook::Block
319
376
  # },
320
377
  # {
321
378
  # :amount=>12.0,
322
- # :source=>"nano_3dcfozsmekr1tr9skf1oa5wbgmxt81qepfdnt7zicq5x3hk65fg4fqj58mbr",
323
- # :block=>:"242A538F36833D1CC78B94E11C766F75818F8B940771335C6C1B8AB880C5BB1D"
379
+ # :source=>Nanook::Account,
380
+ # :block=>Nanook::Block
324
381
  # }
325
382
  # ],
326
- # :nano_3t6k35gi95xu6tergt6p69ck76ogmitsa8mnijtpxm9fkcm736xtoncuohr3=>[
383
+ # Nanook::Account=>[
327
384
  # {
328
385
  # :amount=>106.370018,
329
- # :source=>"nano_13ezf4od79h1tgj9aiu4djzcmmguendtjfuhwfukhuucboua8cpoihmh8byo",
330
- # :block=>:"4C1FEEF0BEA7F50BE35489A1233FE002B212DEA554B55B1B470D78BD8F210C74"
386
+ # :source=>Nanook::Account,
387
+ # :block=>Nanook::Block
331
388
  # }
332
389
  # ]
333
390
  # }
334
- def pending(limit:1000, detailed:false, unit:Nanook.default_unit)
335
- wallet_required!
391
+ #
392
+ # @raise [Nanook::NanoUnitError] if `unit` is invalid
393
+ def pending(limit: 1000, detailed: false, allow_unconfirmed: false, unit: Nanook.default_unit)
394
+ validate_unit!(unit)
336
395
 
337
- unless Nanook::UNITS.include?(unit)
338
- raise ArgumentError.new("Unsupported unit: #{unit}")
339
- end
396
+ params = {
397
+ count: limit,
398
+ include_only_confirmed: !allow_unconfirmed,
399
+ _access: :blocks,
400
+ _coerce: Hash
401
+ }
340
402
 
341
- params = { count: limit }
342
403
  params[:source] = true if detailed
343
404
 
344
- response = rpc(:wallet_pending, params)[:blocks]
345
- response = Nanook::Util.coerce_empty_string_to_type(response, Hash)
405
+ response = rpc(:wallet_pending, params)
346
406
 
347
- return response unless detailed
407
+ unless detailed
408
+
409
+ x = response.map do |account, block_ids|
410
+ blocks = block_ids.map { |block_id| as_block(block_id) }
411
+ [as_account(account), blocks]
412
+ end
413
+
414
+ return Hash[x]
415
+ end
348
416
 
349
417
  # Map the RPC response, which is:
350
418
  # account=>block=>[amount|source] into
351
419
  # account=>[block|amount|source]
352
420
  x = response.map do |account, data|
353
421
  new_data = data.map do |block, amount_and_source|
354
- d = amount_and_source.merge(block: block.to_s)
355
- if unit == :nano
356
- d[:amount] = Nanook::Util.raw_to_NANO(d[:amount])
357
- end
422
+ d = {
423
+ block: as_block(block),
424
+ source: as_account(amount_and_source[:source]),
425
+ amount: amount_and_source[:amount]
426
+ }
427
+ d[:amount] = raw_to_NANO(d[:amount]) if unit == :nano
358
428
  d
359
429
  end
360
430
 
361
- [account, new_data]
431
+ [as_account(account), new_data]
362
432
  end
363
433
 
364
- Hash[x].to_symbolized_hash
434
+ Hash[x]
365
435
  end
366
436
 
367
437
  # Receives a pending payment into an account in the wallet.
@@ -369,7 +439,7 @@ class Nanook
369
439
  # When called with no +block+ argument, the latest pending payment
370
440
  # for the account will be received.
371
441
  #
372
- # Returns a <i>receive</i> block hash id if a receive was successful,
442
+ # Returns a <i>receive</i> block if a receive was successful,
373
443
  # or +false+ if there were no pending payments to receive.
374
444
  #
375
445
  # You can receive a specific pending block if you know it by
@@ -377,19 +447,33 @@ class Nanook
377
447
  #
378
448
  # ==== Examples:
379
449
  #
380
- # wallet.receive(into: "xrb...") # => "9AE2311..."
381
- # wallet.receive("718CC21...", into: "xrb...") # => "9AE2311..."
450
+ # wallet.receive(into: "xrb...") # => Nanook::Block
451
+ # wallet.receive("718CC21...", into: "xrb...") # => Nanook::Block
382
452
  #
383
453
  # @param block (see Nanook::WalletAccount#receive)
384
454
  # @param into [String] account id of account in your wallet to receive the
385
455
  # payment into
386
456
  # @return (see Nanook::WalletAccount#receive)
387
- def receive(block=nil, into:)
388
- wallet_required!
457
+ def receive(block = nil, into:)
389
458
  validate_wallet_contains_account!(into)
390
459
  account(into).receive(block)
391
460
  end
392
461
 
462
+ # Rebroadcast blocks for accounts from wallet starting at frontier down to count to the network.
463
+ #
464
+ # ==== Examples:
465
+ #
466
+ # wallet.republish_blocks # => [Nanook::Block, ...]
467
+ # wallet.republish_blocks(limit: 10) # => [Nanook::Block, ...
468
+ #
469
+ # @param limit [Integer] limit of blocks to publish. Default is 1000.
470
+ # @return [Array<Nanook::Block>] republished blocks
471
+ def republish_blocks(limit: 1000)
472
+ rpc(:wallet_republish, count: limit, _access: :blocks, _coerce: Array).map do |block|
473
+ as_block(block)
474
+ end
475
+ end
476
+
393
477
  # The default representative account id for the wallet. This is the
394
478
  # representative that all new accounts created in this wallet will have.
395
479
  #
@@ -400,11 +484,12 @@ class Nanook
400
484
  #
401
485
  # wallet.default_representative # => "nano_3pc..."
402
486
  #
403
- # @return [String] Representative account of the account
487
+ # @return [Nanook::Account] Representative account. Can be nil.
404
488
  def default_representative
405
- rpc(:wallet_representative)[:representative]
489
+ representative = rpc(:wallet_representative, _access: :representative)
490
+ as_account(representative)
406
491
  end
407
- alias_method :representative, :default_representative
492
+ alias representative default_representative
408
493
 
409
494
  # Sets the default representative for the wallet. A wallet's default
410
495
  # representative is the representative all new accounts created in
@@ -416,23 +501,21 @@ class Nanook
416
501
  #
417
502
  # wallet.change_default_representative("nano_...") # => "nano_..."
418
503
  #
419
- # @param [String] representative the id of the representative account
504
+ # @param representative [String] id of the representative account
420
505
  # to set as this account's representative
421
- # @return [String] the representative account id
422
- # @raise [ArgumentError] if the representative account does not exist
506
+ # @return [Nanook::Account] the representative account
423
507
  # @raise [Nanook::Error] if setting the representative fails
424
508
  def change_default_representative(representative)
425
- unless Nanook::Account.new(@rpc, representative).exists?
426
- raise ArgumentError.new("Representative account does not exist: #{representative}")
509
+ unless as_account(representative).exists?
510
+ raise Nanook::Error, "Representative account does not exist: #{representative}"
427
511
  end
428
512
 
429
- if rpc(:wallet_representative_set, representative: representative)[:set] == 1
430
- representative
431
- else
432
- raise Nanook::Error.new("Setting the representative failed")
433
- end
513
+ raise Nanook::Error, 'Setting the representative failed' \
514
+ unless rpc(:wallet_representative_set, representative: representative, _access: :set) == 1
515
+
516
+ as_account(representative)
434
517
  end
435
- alias_method :change_representative, :change_default_representative
518
+ alias change_representative change_default_representative
436
519
 
437
520
  # Restores a previously created wallet by its seed.
438
521
  # A new wallet will be created on your node (with a new wallet id)
@@ -447,21 +530,68 @@ class Nanook
447
530
  #
448
531
  # @return [Nanook::Wallet] a new wallet
449
532
  # @raise [Nanook::Error] if unsuccessful
450
- def restore(seed, accounts:0)
533
+ def restore(seed, accounts: 0)
534
+ skip_wallet_required!
535
+
451
536
  create
452
537
 
453
- unless change_seed(seed)
454
- raise Nanook::Error.new("Unable to set seed for wallet")
455
- end
538
+ raise Nanook::Error, 'Unable to set seed for wallet' unless change_seed(seed)
456
539
 
457
- if accounts > 0
458
- account.create(accounts)
459
- end
540
+ account.create(accounts) if accounts.positive?
460
541
 
461
542
  self
462
543
  end
463
544
 
464
- # Information about this wallet and all of its accounts.
545
+ # Information ledger information about this wallet's accounts.
546
+ #
547
+ # This call may return results that include unconfirmed blocks, so it should not be
548
+ # used in any processes or integrations requiring only details from blocks confirmed
549
+ # by the network.
550
+ #
551
+ # ==== Examples:
552
+ #
553
+ # wallet.ledger
554
+ #
555
+ # Example response:
556
+ #
557
+ # {
558
+ # Nanook::Account => {
559
+ # frontier: "E71AF3E9DD86BBD8B4620EFA63E065B34D358CFC091ACB4E103B965F95783321",
560
+ # open_block: "643B77F1ECEFBDBE1CC909872964C1DBBE23A6149BD3CEF2B50B76044659B60F",
561
+ # representative_block: "643B77F1ECEFBDBE1CC909872964C1DBBE23A6149BD3CEF2B50B76044659B60F",
562
+ # balance: 1.45,
563
+ # modified_timestamp: 1511476234,
564
+ # block_count: 2
565
+ # },
566
+ # Nanook::Account => { ... }
567
+ # }
568
+ #
569
+ # @param unit (see Nanook::Account#balance)
570
+ # @return [Hash{Nanook::Account=>Hash{Symbol=>Nanook::Block|Integer|Float|Time}}] ledger.
571
+ # @raise [Nanook::NanoUnitError] if `unit` is invalid
572
+ def ledger(unit: Nanook.default_unit)
573
+ validate_unit!(unit)
574
+
575
+ response = rpc(:wallet_ledger, _access: :accounts, _coerce: Hash)
576
+
577
+ accounts = response.map do |account_id, data|
578
+ data[:frontier] = as_block(data[:frontier])
579
+ data[:open_block] = as_block(data[:open_block])
580
+ data[:representative_block] = as_block(data[:representative_block])
581
+ data[:balance] = raw_to_NANO(data[:balance]) if unit == :nano
582
+ data[:last_modified_at] = as_time(data.delete(:modified_timestamp))
583
+
584
+ [as_account(account_id), data]
585
+ end
586
+
587
+ Hash[accounts]
588
+ end
589
+
590
+ # Information about this wallet.
591
+ #
592
+ # This call may return results that include unconfirmed blocks, so it should not be
593
+ # used in any processes or integrations requiring only details from blocks confirmed
594
+ # by the network.
465
595
  #
466
596
  # ==== Examples:
467
597
  #
@@ -470,42 +600,68 @@ class Nanook
470
600
  # Example response:
471
601
  #
472
602
  # {
473
- # id: "2C3C570EA8898443C0FD04A1C385A3E3A8C985AD792635FCDCEBB30ADF6A0570",
474
- # accounts: [
475
- # {
476
- # id: "nano_11119gbh8hb4hj1duf7fdtfyf5s75okzxdgupgpgm1bj78ex3kgy7frt3s9n"
477
- # frontier: "E71AF3E9DD86BBD8B4620EFA63E065B34D358CFC091ACB4E103B965F95783321",
478
- # open_block: "643B77F1ECEFBDBE1CC909872964C1DBBE23A6149BD3CEF2B50B76044659B60F",
479
- # representative_block: "643B77F1ECEFBDBE1CC909872964C1DBBE23A6149BD3CEF2B50B76044659B60F",
480
- # balance: 1.45,
481
- # modified_timestamp: 1511476234,
482
- # block_count: 2
483
- # },
484
- # { ... }
485
- # ]
486
- # }
603
+ # balance: 1.0,
604
+ # pending: 2.3
605
+ # accounts_count: 3,
606
+ # adhoc_count: 1,
607
+ # deterministic_count: 2,
608
+ # deterministic_index: 2
609
+ # }
487
610
  #
488
- # @param unit (see #balance)
489
- # @return [Hash{Symbol=>String|Array<Hash{Symbol=>String|Integer|Float}>}] information about the wallet.
490
- # See {Nanook::Account#info} for details of what is returned for each account.
611
+ # @param unit (see Nanook::Account#balance)
612
+ # @return [Hash{Symbol=>Integer|Float}] information about the wallet.
613
+ # @raise [Nanook::NanoUnitError] if `unit` is invalid
491
614
  def info(unit: Nanook.default_unit)
492
- unless Nanook::UNITS.include?(unit)
493
- raise ArgumentError.new("Unsupported unit: #{unit}")
494
- end
615
+ validate_unit!(unit)
495
616
 
496
- wallet_required!
497
- accounts = rpc(:wallet_ledger)[:accounts].map do |account_id, payload|
498
- payload[:id] = account_id
499
- if unit == :nano
500
- payload[:balance] = Nanook::Util.raw_to_NANO(payload[:balance])
501
- end
502
- payload
617
+ response = rpc(:wallet_info, _coerce: Hash)
618
+
619
+ if unit == :nano
620
+ response[:balance] = raw_to_NANO(response[:balance])
621
+ response[:pending] = raw_to_NANO(response[:pending])
503
622
  end
504
623
 
505
- {
506
- id: @wallet,
507
- accounts: accounts
508
- }.to_symbolized_hash
624
+ response
625
+ end
626
+
627
+ # Reports send/receive information for accounts in wallet. Change blocks are skipped,
628
+ # open blocks will appear as receive. Response will start with most recent blocks
629
+ # according to local ledger.
630
+ #
631
+ # ==== Example:
632
+ #
633
+ # wallet.history
634
+ #
635
+ # Example response:
636
+ #
637
+ # [
638
+ # {
639
+ # "type": "send",
640
+ # "account": Nanook::Account,
641
+ # "amount": 3.2,
642
+ # "block_account": Nanook::Account,
643
+ # "hash": Nanook::Block,
644
+ # "local_timestamp": Time
645
+ # },
646
+ # {
647
+ # ...
648
+ # }
649
+ # ]
650
+ #
651
+ # @param unit (see #balance)
652
+ # @return [Array<Hash{Symbol=>String|Nanook::Account|Nanook::WalletAccount|Nanook::Block|Integer|Float|Time}>]
653
+ # @raise [Nanook::NanoUnitError] if `unit` is invalid
654
+ def history(unit: Nanook.default_unit)
655
+ validate_unit!(unit)
656
+
657
+ rpc(:wallet_history, _access: :history, _coerce: Array).map do |h|
658
+ h[:account] = account(h[:account])
659
+ h[:block_account] = as_account(h[:block_account])
660
+ h[:amount] = raw_to_NANO(h[:amount]) if unit == :nano
661
+ h[:block] = as_block(h.delete(:hash))
662
+ h[:local_timestamp] = as_time(h[:local_timestamp])
663
+ h
664
+ end
509
665
  end
510
666
 
511
667
  # Locks the wallet. A locked wallet cannot pocket pending transactions or make payments. See {#unlock}.
@@ -516,9 +672,7 @@ class Nanook
516
672
  #
517
673
  # @return [Boolean] indicates if the wallet was successfully locked
518
674
  def lock
519
- wallet_required!
520
- response = rpc(:wallet_lock)
521
- !response.empty? && response[:locked] == 1
675
+ rpc(:wallet_lock, _access: :locked) == 1
522
676
  end
523
677
 
524
678
  # Returns +true+ if the wallet is locked.
@@ -529,9 +683,7 @@ class Nanook
529
683
  #
530
684
  # @return [Boolean] indicates if the wallet is locked
531
685
  def locked?
532
- wallet_required!
533
- response = rpc(:wallet_locked)
534
- !response.empty? && response[:locked] != 0
686
+ rpc(:wallet_locked, _access: :locked) == 1
535
687
  end
536
688
 
537
689
  # Unlocks a previously locked wallet.
@@ -541,9 +693,8 @@ class Nanook
541
693
  # wallet.unlock("new_pass") #=> true
542
694
  #
543
695
  # @return [Boolean] indicates if the unlocking action was successful
544
- def unlock(password)
545
- wallet_required!
546
- rpc(:password_enter, password: password)[:valid] == 1
696
+ def unlock(password = nil)
697
+ rpc(:password_enter, password: password, _access: :valid) == 1
547
698
  end
548
699
 
549
700
  # Changes the password for a wallet.
@@ -553,33 +704,73 @@ class Nanook
553
704
  # wallet.change_password("new_pass") #=> true
554
705
  # @return [Boolean] indicates if the action was successful
555
706
  def change_password(password)
556
- wallet_required!
557
- rpc(:password_change, password: password)[:changed] == 1
707
+ rpc(:password_change, password: password, _access: :changed) == 1
708
+ end
709
+
710
+ # Tells the node to look for pending blocks for any account in the wallet.
711
+ #
712
+ # ==== Example:
713
+ #
714
+ # wallet.search_pending #=> true
715
+ # @return [Boolean] indicates if the action was successful
716
+ def search_pending
717
+ rpc(:search_pending, _access: :started) == 1
718
+ end
719
+
720
+ # Returns a list of pairs of {Nanook::WalletAccount} and work for wallet.
721
+ #
722
+ # ==== Example:
723
+ #
724
+ # wallet.work
725
+ #
726
+ # ==== Example response:
727
+ #
728
+ # {
729
+ # Nanook::WalletAccount: "432e5cf728c90f4f",
730
+ # Nanook::WalletAccount: "4efec5f63fc902cf"
731
+ # }
732
+ # @return [Boolean] indicates if the action was successful
733
+ def work
734
+ hash = rpc(:wallet_work_get, _access: :works, _coerce: Hash).map do |account_id, work|
735
+ [as_wallet_account(account_id), work]
736
+ end
737
+
738
+ Hash[hash]
558
739
  end
559
740
 
560
741
  private
561
742
 
562
- def rpc(action, params={})
563
- p = @wallet.nil? ? {} : { wallet: @wallet }
564
- @rpc.call(action, p.merge(params))
743
+ def rpc(action, params = {})
744
+ check_wallet_required!
745
+
746
+ p = { wallet: @wallet }.compact
747
+ @rpc.call(action, p.merge(params)).tap { reset_skip_wallet_required! }
565
748
  end
566
749
 
567
- def wallet_required!
568
- if @wallet.nil?
569
- raise ArgumentError.new("Wallet must be present")
570
- end
750
+ def skip_wallet_required!
751
+ @skip_wallet_required_check = true
752
+ end
753
+
754
+ def reset_skip_wallet_required!
755
+ @skip_wallet_required_check = false
756
+ end
757
+
758
+ def check_wallet_required!
759
+ return if @wallet || @skip_wallet_required_check
760
+
761
+ raise ArgumentError, 'Wallet must be present'
571
762
  end
572
763
 
573
764
  def validate_wallet_contains_account!(account)
574
765
  @known_valid_accounts ||= []
575
766
  return if @known_valid_accounts.include?(account)
576
767
 
577
- if contains?(account)
578
- @known_valid_accounts << account
579
- else
580
- raise ArgumentError.new("Account does not exist in wallet. Account: #{account}, wallet: #{@wallet}")
768
+ unless contains?(account)
769
+ raise ArgumentError,
770
+ "Account does not exist in wallet. Account: #{account}, wallet: #{@wallet}"
581
771
  end
582
- end
583
772
 
773
+ @known_valid_accounts << account
774
+ end
584
775
  end
585
776
  end