Class: Nanook::Wallet

Inherits:
Object
  • Object
show all
Includes:
Util
Defined in:
lib/nanook/wallet.rb

Overview

The Nanook::Wallet class lets you manage your nano wallets. Your node will need the enable_control setting enabled.

Wallet seeds vs ids

Your wallets each have an id as well as a seed. Both are 32-byte uppercase hex strings that look like this:

000D1BAEC8EC208142C99059B393051BAC8380F9B5A2E6B2489A277D81789F3F

This class uses wallet ids to identify your wallet. A wallet id only exists locally on the nano node that it was created on. The person who knows this id can only perform all read and write actions against the wallet and all accounts inside the wallet from the same nano node that it was created on. This makes wallet ids fairly safe to use as a person needs to know your wallet id as well as have access to run RPC commands against your nano node to be able to control your accounts.

A seed on the other hand can be used to link any wallet to another wallet’s accounts, from anywhere in the nano network. This happens by setting a wallet’s seed to be the same as a previous wallet’s seed. When a wallet has the same seed as another wallet, any accounts created in the second wallet will be the same accounts as those that were created in the previous wallet, and the new wallet’s owner will also gain ownership of the previous wallet’s accounts. Note, that the two wallets will have different ids, but the same seed.

Nanook is based on the Nano RPC, which uses wallet ids and not seeds. The RPC and therefore Nanook cannot tell you what a wallet’s seed is, only its id. Knowing a wallet’s seed is very useful for if you ever want to restore the wallet anywhere else on the nano network besides the node you originally created it on. The nano command line interface (CLI) is the only method for discovering a wallet’s seed. See the https://docs.nano.org/commands/command-line-interface/#-wallet_decrypt_unsafe-walletwallet-passwordpassword.

Initializing

Initialize this class through the convenient #wallet method:

nanook = Nanook.new
wallet = nanook.wallet(wallet_id)

Or compose the longhand way like this:

rpc_conn = Nanook::Rpc.new
wallet = Nanook::Wallet.new(rpc_conn, wallet_id)

Constant Summary

Constants included from Util

Util::STEP

Instance Method Summary collapse

Constructor Details

#initialize(rpc, wallet = nil) ⇒ Wallet



55
56
57
58
# File 'lib/nanook/wallet.rb', line 55

def initialize(rpc, wallet = nil)
  @rpc = rpc
  @wallet = wallet.to_s if wallet
end

Instance Method Details

#==(other) ⇒ Boolean Also known as: eql?



67
68
69
70
# File 'lib/nanook/wallet.rb', line 67

def ==(other)
  other.class == self.class &&
    other.id == id
end

#account(account = nil) ⇒ Nanook::WalletAccount

Returns the given account in the wallet as a Nanook::WalletAccount instance to let you start working with it.

Call with no account argument if you wish to create a new account in the wallet, like this:

wallet..create     # => Nanook::WalletAccount

See Nanook::WalletAccount for all the methods you can call on the account object returned.

Examples:

wallet.("nano_...") # => Nanook::WalletAccount
wallet..create     # => Nanook::WalletAccount

Raises:

  • (ArgumentError)

    if the wallet does not contain the account



103
104
105
106
# File 'lib/nanook/wallet.rb', line 103

def ( = nil)
  check_wallet_required!
  ()
end

#accountsArray<Nanook::WalletAccount>

Array of Nanook::WalletAccount instances of accounts in the wallet.

See Nanook::WalletAccount for all the methods you can call on the account objects returned.

Example:

wallet.accounts # => [Nanook::WalletAccount, Nanook::WalletAccount...]


118
119
120
121
122
# File 'lib/nanook/wallet.rb', line 118

def accounts
  rpc(:account_list, _access: :accounts, _coerce: Array).map do ||
    ()
  end
end

#balance(account_break_down: false, unit: Nanook.default_unit) ⇒ Hash{Symbol=>Integer|Float|Hash}

Balance of all accounts in the wallet, optionally breaking the balances down by account.

Examples:

wallet.balance

Example response:

{
  "balance"=>5,
  "pending"=>0.001
}

Asking for the balances to be returned in raw instead of NANO.

wallet.balance(unit: :raw)

Example response:

{
  "balance"=>5000000000000000000000000000000,
  "pending"=>1000000000000000000000000000
}

Asking for totals to be broken down by account:

wallet.balance(account_break_down: true)

Example response:

{
  "nano_3e3j5tkog48pnny9dmfzj1r16pg8t1e76dz5tmac6iq689wyjfpi00000000"=>{
    "balance"=>2.5,
    "pending"=>1
  },
  "nano_1e5aqegc1jb7qe964u4adzmcezyo6o146zb8hm6dft8tkp79za3sxwjym5rx"=>{
    "balance"=>51.4,
    "pending"=>0
  },
}

Raises:



192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
# File 'lib/nanook/wallet.rb', line 192

def balance(account_break_down: false, unit: Nanook.default_unit)
  validate_unit!(unit)

  if 
    return rpc(:wallet_balances, _access: :balances, _coerce: Hash).tap do |r|
      if unit == :nano
        r.each do |, _balances|
          r[][:balance] = raw_to_NANO(r[][:balance])
          r[][:pending] = raw_to_NANO(r[][:pending])
        end
      end
    end
  end

  response = rpc(:wallet_info, _coerce: Hash).slice(:balance, :pending)
  return response unless unit == :nano

  {
    balance: raw_to_NANO(response[:balance]),
    pending: raw_to_NANO(response[:pending])
  }
end

#change_default_representative(representative) ⇒ Nanook::Account Also known as: change_representative

Sets the default representative for the wallet. A wallet’s default representative is the representative all new accounts created in the wallet will have. Changing the default representative for a wallet does not change the representatives for existing accounts in the wallet.

Example:

wallet.change_default_representative("nano_...") # => "nano_..."

Raises:



503
504
505
506
507
508
509
510
511
512
# File 'lib/nanook/wallet.rb', line 503

def change_default_representative(representative)
  unless (representative).exists?
    raise Nanook::Error, "Representative account does not exist: #{representative}"
  end

  raise Nanook::Error, 'Setting the representative failed' \
    unless rpc(:wallet_representative_set, representative: representative, _access: :set) == 1

  (representative)
end

#change_password(password) ⇒ Boolean

Changes the password for a wallet.

Example:

wallet.change_password("new_pass") #=> true


701
702
703
# File 'lib/nanook/wallet.rb', line 701

def change_password(password)
  rpc(:password_change, password: password, _access: :changed) == 1
end

#change_seed(seed) ⇒ Boolean

Changes a wallet’s seed.

It’s recommended to only change the seed of a wallet that contains no accounts. This will clear all deterministic accounts in the wallet. To restore accounts after changing the seed, see Nanook::WalletAccount#create.

Example:

wallet.change_seed("000D1BA...") # => true
wallet..create(5) # Restores first 5 accounts for wallet with new seed


228
229
230
# File 'lib/nanook/wallet.rb', line 228

def change_seed(seed)
  rpc(:wallet_change_seed, seed: seed).key?(:success)
end

#contains?(account) ⇒ Boolean

Will return true if the account exists in the wallet.

Example:

wallet.contains?("nano_...") # => true


296
297
298
# File 'lib/nanook/wallet.rb', line 296

def contains?()
  rpc(:wallet_contains, account: , _access: :exists) == 1
end

#createNanook::Wallet

Creates a new wallet.

The wallet will be created only on this node. It’s important that if you intend to add funds to accounts in this wallet that you backup the wallet seed in order to restore the wallet in future. The nano command line interface (CLI) is the only method for backing up a wallet’s seed. See the –wallet_decrypt_unsafe CLI command.

Example:

Nanook.new.wallet.create # => Nanook::Wallet


246
247
248
249
250
# File 'lib/nanook/wallet.rb', line 246

def create
  skip_wallet_required!
  @wallet = rpc(:wallet_create, _access: :wallet)
  self
end

#default_representativeNanook::Account Also known as: representative

The default representative account id for the wallet. This is the representative that all new accounts created in this wallet will have.

Changing the default representative for a wallet does not change the representatives for any accounts that have been created.

Example:

wallet.default_representative # => "nano_3pc..."


483
484
485
486
# File 'lib/nanook/wallet.rb', line 483

def default_representative
  representative = rpc(:wallet_representative, _access: :representative)
  (representative) if representative
end

#destroyBoolean

Destroys the wallet.

Example:

wallet.destroy # => true


259
260
261
# File 'lib/nanook/wallet.rb', line 259

def destroy
  rpc(:wallet_destroy, _access: :destroyed) == 1
end

#exists?Boolean

Returns true if wallet exists on the node.

Example:

wallet.exists? # => true


282
283
284
285
286
287
# File 'lib/nanook/wallet.rb', line 282

def exists?
  export
  true
rescue Nanook::NodeRpcError
  false
end

#exportString

Generates a String containing a JSON representation of your wallet.

Example:

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"


271
272
273
# File 'lib/nanook/wallet.rb', line 271

def export
  rpc(:wallet_export, _access: :json)
end

#hashInteger

The hash value is used along with #eql? by the Hash class to determine if two objects reference the same hash key.



77
78
79
# File 'lib/nanook/wallet.rb', line 77

def hash
  id.hash
end

#history(unit: Nanook.default_unit) ⇒ Array<Hash{Symbol=>String|Nanook::Account|Nanook::WalletAccount|Nanook::Block|Integer|Float|Time}>

Reports send/receive information for accounts in wallet. Change blocks are skipped, open blocks will appear as receive. Response will start with most recent blocks according to local ledger.

Example:

wallet.history

Example response:

[
  {
    "type": "send",
    "account": Nanook::,
    "amount": 3.2,
    "block_account": Nanook::,
    "hash": Nanook::Block,
    "local_timestamp": Time
  },
  {
    ...
  }
]

Raises:



649
650
651
652
653
654
655
656
657
658
659
660
# File 'lib/nanook/wallet.rb', line 649

def history(unit: Nanook.default_unit)
  validate_unit!(unit)

  rpc(:wallet_history, _access: :history, _coerce: Array).map do |h|
    h[:account] = (h[:account])
    h[:block_account] = (h[:block_account])
    h[:amount] = raw_to_NANO(h[:amount]) if unit == :nano
    h[:block] = as_block(h.delete(:hash))
    h[:local_timestamp] = as_time(h[:local_timestamp])
    h
  end
end

#idString



61
62
63
# File 'lib/nanook/wallet.rb', line 61

def id
  @wallet
end

#info(unit: Nanook.default_unit) ⇒ Hash{Symbol=>Integer|Float}

Information about this wallet.

This call may return results that include unconfirmed blocks, so it should not be used in any processes or integrations requiring only details from blocks confirmed by the network.

Examples:

wallet.info

Example response:

{
  balance: 1.0,
  pending: 2.3
  accounts_count: 3,
  adhoc_count: 1,
  deterministic_count: 2,
  deterministic_index: 2

}

Raises:



609
610
611
612
613
614
615
616
617
618
619
620
# File 'lib/nanook/wallet.rb', line 609

def info(unit: Nanook.default_unit)
  validate_unit!(unit)

  response = rpc(:wallet_info, _coerce: Hash)

  if unit == :nano
    response[:balance] = raw_to_NANO(response[:balance])
    response[:pending] = raw_to_NANO(response[:pending])
  end

  response
end

#ledger(unit: Nanook.default_unit) ⇒ Hash{Nanook::Account=>Hash{Symbol=>Nanook::Block|Integer|Float|Time}}

Information ledger information about this wallet’s accounts.

This call may return results that include unconfirmed blocks, so it should not be used in any processes or integrations requiring only details from blocks confirmed by the network.

Examples:

wallet.ledger

Example response:

{
  Nanook:: => {
    frontier: "E71AF3E9DD86BBD8B4620EFA63E065B34D358CFC091ACB4E103B965F95783321",
    open_block: "643B77F1ECEFBDBE1CC909872964C1DBBE23A6149BD3CEF2B50B76044659B60F",
    representative_block: "643B77F1ECEFBDBE1CC909872964C1DBBE23A6149BD3CEF2B50B76044659B60F",
    balance: 1.45,
    modified_timestamp: 1511476234,
    block_count: 2
  },
  Nanook:: => { ... }
}

Raises:



567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
# File 'lib/nanook/wallet.rb', line 567

def ledger(unit: Nanook.default_unit)
  validate_unit!(unit)

  response = rpc(:wallet_ledger, _access: :accounts, _coerce: Hash)

  accounts = response.map do |, data|
    data[:frontier] = as_block(data[:frontier]) if data[:frontier]
    data[:open_block] = as_block(data[:open_block]) if data[:open_block]
    data[:representative_block] = as_block(data[:representative_block]) if data[:representative_block]
    data[:balance] = raw_to_NANO(data[:balance]) if unit == :nano && data[:balance]
    data[:last_modified_at] = as_time(data.delete(:modified_timestamp))

    [(), data]
  end

  Hash[accounts]
end

#lockBoolean

Locks the wallet. A locked wallet cannot pocket pending transactions or make payments. See #unlock.

Example:

wallet.lock #=> true


669
670
671
# File 'lib/nanook/wallet.rb', line 669

def lock
  rpc(:wallet_lock, _access: :locked) == 1
end

#locked?Boolean

Returns true if the wallet is locked.

Example:

wallet.locked? #=> false


680
681
682
# File 'lib/nanook/wallet.rb', line 680

def locked?
  rpc(:wallet_locked, _access: :locked) == 1
end

#move_accounts(wallet, accounts) ⇒ Boolean

Move accounts from another Nanook::Wallet on the node to this Nanook::Wallet.

Example:

wallet.move_accounts("0023200...", ["nano_3e3j5...", "nano_5f2a1..."]) # => true


131
132
133
# File 'lib/nanook/wallet.rb', line 131

def move_accounts(wallet, accounts)
  rpc(:account_move, source: wallet, accounts: accounts, _access: :moved) == 1
end

#pay(from:, to:, amount:, id:, unit: Nanook.default_unit) ⇒ Nanook::Block

Makes a payment from an account in your wallet to another account on the nano network.

Note, there may be a delay in receiving a response due to Proof of Work being done. From the Nano RPC:

Proof of Work is precomputed for one transaction in the background. If it has been a while since your last transaction it will send instantly, the next one will need to wait for Proof of Work to be generated.

Examples:

wallet.pay(from: "nano_...", to: "nano_...", amount: 1.1, id: "myUniqueId123") # => "9AE2311..."
wallet.pay(from: "nano_...", to: "nano_...", amount: 54000000000000, unit: :raw, id: "myUniqueId123")
  # => "9AE2311..."

Raises:



330
331
332
333
# File 'lib/nanook/wallet.rb', line 330

def pay(from:, to:, amount:, id:, unit: Nanook.default_unit)
  validate_wallet_contains_account!(from)
  (from).pay(to: to, amount: amount, unit: unit, id: id)
end

#pending(limit: 1000, detailed: false, unit: Nanook.default_unit) ⇒ Object

Information about pending blocks (payments) that are waiting to be received by accounts in this wallet.

See also the #receive method of this class for how to receive a pending payment.

Examples:

wallet.pending

Example response:

{
  Nanook::=>[
    Nanook::Block,
    Nanook::Block"
  ],
  Nanook::Account=>[
    Nanook::Block
  ]
}

Asking for more information:

wallet.pending(detailed: true)

Example response:

{
  Nanook::=>[
    {
      :amount=>6.0,
      :source=>Nanook::,
      :block=>Nanook::Block
    },
    {
      :amount=>12.0,
      :source=>Nanook::,
      :block=>Nanook::Block
    }
  ],
  Nanook::=>[
    {
      :amount=>106.370018,
      :source=>Nanook::,
      :block=>Nanook::Block
    }
  ]
}

Raises:



389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
# File 'lib/nanook/wallet.rb', line 389

def pending(limit: 1000, detailed: false, unit: Nanook.default_unit)
  validate_unit!(unit)

  params = {
    count: limit,
    _access: :blocks,
    _coerce: Hash
  }

  params[:source] = true if detailed

  response = rpc(:wallet_pending, params)

  unless detailed

    x = response.map do |, block_ids|
      blocks = block_ids.map { |block_id| as_block(block_id) }
      [(), blocks]
    end

    return Hash[x]
  end

  # Map the RPC response, which is:
  # account=>block=>[amount|source] into
  # account=>[block|amount|source]
  x = response.map do |, data|
    new_data = data.map do |block, amount_and_source|
      d = {
        block: as_block(block),
        source: (amount_and_source[:source]),
        amount: amount_and_source[:amount]
      }
      d[:amount] = raw_to_NANO(d[:amount]) if unit == :nano
      d
    end

    [(), new_data]
  end

  Hash[x]
end

#receive(block = nil, into:) ⇒ Nanook::Block, false

Receives a pending payment into an account in the wallet.

When called with no block argument, the latest pending payment for the account will be received.

Returns a receive block if a receive was successful, or false if there were no pending payments to receive.

You can receive a specific pending block if you know it by passing the block has in as an argument.

Examples:

wallet.receive(into: "xrb...")               # => Nanook::Block
wallet.receive("718CC21...", into: "xrb...") # => Nanook::Block


452
453
454
455
# File 'lib/nanook/wallet.rb', line 452

def receive(block = nil, into:)
  validate_wallet_contains_account!(into)
  (into).receive(block)
end

#remove_account(account) ⇒ Boolean

Remove an Account from this Nanook::Wallet.

Example:

wallet.("nano_3e3j5...") # => true


142
143
144
# File 'lib/nanook/wallet.rb', line 142

def ()
  rpc(:account_remove, account: , _access: :removed) == 1
end

#republish_blocks(limit: 1000) ⇒ Array<Nanook::Block>

Rebroadcast blocks for accounts from wallet starting at frontier down to count to the network.

Examples:

wallet.republish_blocks             # => [Nanook::Block, ...]
wallet.republish_blocks(limit: 10)  # => [Nanook::Block, ...


466
467
468
469
470
# File 'lib/nanook/wallet.rb', line 466

def republish_blocks(limit: 1000)
  rpc(:wallet_republish, count: limit, _access: :blocks, _coerce: Array).map do |block|
    as_block(block)
  end
end

#restore(seed, accounts: 0) ⇒ Nanook::Wallet

Restores a previously created wallet by its seed. A new wallet will be created on your node (with a new wallet id) and will have its seed set to the given seed.

Example:

Nanook.new.wallet.restore(seed) # => Nanook::Wallet

Raises:



528
529
530
531
532
533
534
535
536
537
538
# File 'lib/nanook/wallet.rb', line 528

def restore(seed, accounts: 0)
  skip_wallet_required!

  create

  raise Nanook::Error, 'Unable to set seed for wallet' unless change_seed(seed)

  .create(accounts) if accounts.positive?

  self
end

#search_pendingBoolean

Tells the node to look for pending blocks for any account in the wallet.

Example:

wallet.search_pending #=> true


711
712
713
# File 'lib/nanook/wallet.rb', line 711

def search_pending
  rpc(:search_pending, _access: :started) == 1
end

#to_sString Also known as: inspect



301
302
303
# File 'lib/nanook/wallet.rb', line 301

def to_s
  "#{self.class.name}(id: \"#{short_id}\")"
end

#unlock(password = nil) ⇒ Boolean

Unlocks a previously locked wallet.

Example:

wallet.unlock("new_pass") #=> true


691
692
693
# File 'lib/nanook/wallet.rb', line 691

def unlock(password = nil)
  rpc(:password_enter, password: password, _access: :valid) == 1
end

#workBoolean

Returns a list of pairs of Nanook::WalletAccount and work for wallet.

Example:

wallet.work

Example response:

{
  Nanook::WalletAccount: "432e5cf728c90f4f",
  Nanook::WalletAccount: "4efec5f63fc902cf"
}


728
729
730
731
732
733
734
# File 'lib/nanook/wallet.rb', line 728

def work
  hash = rpc(:wallet_work_get, _access: :works, _coerce: Hash).map do |, work|
    [(), work]
  end

  Hash[hash]
end