nanook 2.5.0 → 4.0.0

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