Class: Nanook::Wallet

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

Overview

The Nanook::Wallet class lets you manage your nano wallets, as well as some account-specific things like making and receiving payments.

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 –wallet_decrypt_unsafe CLI command.

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)

Instance Method Summary collapse

Constructor Details

#initialize(rpc, wallet) ⇒ Wallet


52
53
54
55
# File 'lib/nanook/wallet.rb', line 52

def initialize(rpc, wallet)
  @rpc = rpc
  @wallet = wallet
end

Instance Method Details

#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.("xrb_...") # => Nanook::WalletAccount
wallet..create     # => Nanook::WalletAccount

Raises:

  • (ArgumentError)

    if the wallet does no contain the account


79
80
81
# File 'lib/nanook/wallet.rb', line 79

def (=nil)
  Nanook::WalletAccount.new(@rpc, @wallet, )
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...]

93
94
95
96
97
98
99
# File 'lib/nanook/wallet.rb', line 93

def accounts
  wallet_required!
  response = rpc(:account_list)[:accounts]
  Nanook::Util.coerce_empty_string_to_type(response, Array).map do ||
    Nanook::WalletAccount.new(@rpc, @wallet, )
  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:

{
  "xrb_3e3j5tkog48pnny9dmfzj1r16pg8t1e76dz5tmac6iq689wyjfpi00000000"=>{
    "balance"=>2.5,
    "pending"=>1
  },
  "xrb_1e5aqegc1jb7qe964u4adzmcezyo6o146zb8hm6dft8tkp79za3sxwjym5rx"=>{
    "balance"=>51.4,
    "pending"=>0
  },
}

146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
# File 'lib/nanook/wallet.rb', line 146

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

  unless Nanook::UNITS.include?(unit)
    raise ArgumentError.new("Unsupported unit: #{unit}")
  end

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

  rpc(:wallet_balance_total).tap do |r|
    if unit == :nano
      r[:balance] = Nanook::Util.raw_to_NANO(r[:balance])
      r[:pending] = Nanook::Util.raw_to_NANO(r[:pending])
    end
  end
end

#change_default_representative(representative) ⇒ String 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("xrb_...") # => "xrb_..."

Raises:

  • (ArgumentError)

    if the representative account does not exist

  • (Nanook::Error)

    if setting the representative fails


421
422
423
424
425
426
427
428
429
430
431
# File 'lib/nanook/wallet.rb', line 421

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

  if rpc(:wallet_representative_set, representative: representative)[:set] == 1
    representative
  else
    raise Nanook::Error.new("Setting the representative failed")
  end
end

#change_password(password) ⇒ Boolean

Changes the password for a wallet.

Example:

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

539
540
541
542
# File 'lib/nanook/wallet.rb', line 539

def change_password(password)
  wallet_required!
  rpc(:password_change, password: password)[:changed] == 1
end

#change_seed(seed) ⇒ Boolean

Changes a wallet's seed.

Example:

wallet.change_seed("000D1BA...") # => true

180
181
182
183
# File 'lib/nanook/wallet.rb', line 180

def change_seed(seed)
  wallet_required!
  rpc(:wallet_change_seed, seed: seed).has_key?(:success)
end

#contains?(account) ⇒ Boolean

Will return true if the account exists in the wallet.

Example:

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

234
235
236
237
238
# File 'lib/nanook/wallet.rb', line 234

def contains?()
  wallet_required!
  response = rpc(:wallet_contains, account: )
  !response.empty? && response[: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

199
200
201
202
# File 'lib/nanook/wallet.rb', line 199

def create
  @wallet = rpc(:wallet_create)[:wallet]
  self
end

#default_representativeString 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 # => "xrb_3pc..."

401
402
403
# File 'lib/nanook/wallet.rb', line 401

def default_representative
  rpc(:wallet_representative)[:representative]
end

#destroyBoolean

Destroys the wallet.

Example:

wallet.destroy # => true

211
212
213
214
215
# File 'lib/nanook/wallet.rb', line 211

def destroy
  wallet_required!
  rpc(:wallet_destroy)
  true
end

#exportObject

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"

222
223
224
225
# File 'lib/nanook/wallet.rb', line 222

def export
  wallet_required!
  rpc(:wallet_export)[:json]
end

#idString


241
242
243
# File 'lib/nanook/wallet.rb', line 241

def id
  @wallet
end

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

Information about this wallet and all of its accounts.

Examples:

wallet.info

Example response:

{
  id: "2C3C570EA8898443C0FD04A1C385A3E3A8C985AD792635FCDCEBB30ADF6A0570",
  accounts: [
    {
      id: "xrb_11119gbh8hb4hj1duf7fdtfyf5s75okzxdgupgpgm1bj78ex3kgy7frt3s9n"
      frontier: "E71AF3E9DD86BBD8B4620EFA63E065B34D358CFC091ACB4E103B965F95783321",
      open_block: "643B77F1ECEFBDBE1CC909872964C1DBBE23A6149BD3CEF2B50B76044659B60F",
      representative_block: "643B77F1ECEFBDBE1CC909872964C1DBBE23A6149BD3CEF2B50B76044659B60F",
      balance: 1.45,
      modified_timestamp: 1511476234,
      block_count: 2
    },
    { ... }
  ]
}

488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
# File 'lib/nanook/wallet.rb', line 488

def info(unit: Nanook.default_unit)
  unless Nanook::UNITS.include?(unit)
    raise ArgumentError.new("Unsupported unit: #{unit}")
  end

  wallet_required!
  accounts = rpc(:wallet_ledger)[:accounts].map do |, payload|
    payload[:id] = 
    if unit == :nano
      payload[:balance] = Nanook::Util.raw_to_NANO(payload[:balance])
    end
    payload
  end

  {
    id: @wallet,
    accounts: accounts
  }.to_symbolized_hash
end

#inspectString


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

def inspect
  "#{self.class.name}(id: \"#{id}\", object_id: \"#{"0x00%x" % (object_id << 1)}\")"
end

#locked?Boolean

Returns true if the wallet is locked.

Example:

wallet.locked? #=> false

515
516
517
518
519
# File 'lib/nanook/wallet.rb', line 515

def locked?
  wallet_required!
  response = rpc(:wallet_locked)
  !response.empty? && response[:locked] != 0
end

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

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: "xrb_...", to: "xrb_...", amount: 1.1, id: "myUniqueId123") # => "9AE2311..."
wallet.pay(from: "xrb_...", to: "xrb_...", amount: 54000000000000, unit: :raw, id: "myUniqueId123") # => "9AE2311..."

Raises:


273
274
275
276
277
# File 'lib/nanook/wallet.rb', line 273

def pay(from:, to:, amount:, unit: Nanook.default_unit, id:)
  wallet_required!
  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:

{
  :xrb_1111111111111111111111111111111111111111111111111117353trpda=>[
    "142A538F36833D1CC78B94E11C766F75818F8B940771335C6C1B8AB880C5BB1D",
    "718CC2121C3E641059BC1C2CFC45666C99E8AE922F7A807B7D07B62C995D79E2"
  ],
  :xrb_3t6k35gi95xu6tergt6p69ck76ogmitsa8mnijtpxm9fkcm736xtoncuohr3=>[
    "4C1FEEF0BEA7F50BE35489A1233FE002B212DEA554B55B1B470D78BD8F210C74"
  ]
}

Asking for more information:

wallet.pending(detailed: true)

Example response:

{
  :xrb_1111111111111111111111111111111111111111111111111117353trpda=>[
    {
      :amount=>6.0,
      :source=>"xrb_3dcfozsmekr1tr9skf1oa5wbgmxt81qepfdnt7zicq5x3hk65fg4fqj58mbr",
      :block=>:"142A538F36833D1CC78B94E11C766F75818F8B940771335C6C1B8AB880C5BB1D"
    },
    {
      :amount=>12.0,
      :source=>"xrb_3dcfozsmekr1tr9skf1oa5wbgmxt81qepfdnt7zicq5x3hk65fg4fqj58mbr",
      :block=>:"242A538F36833D1CC78B94E11C766F75818F8B940771335C6C1B8AB880C5BB1D"
    }
  ],
  :xrb_3t6k35gi95xu6tergt6p69ck76ogmitsa8mnijtpxm9fkcm736xtoncuohr3=>[
    {
      :amount=>106.370018,
      :source=>"xrb_13ezf4od79h1tgj9aiu4djzcmmguendtjfuhwfukhuucboua8cpoihmh8byo",
      :block=>:"4C1FEEF0BEA7F50BE35489A1233FE002B212DEA554B55B1B470D78BD8F210C74"
    }
  ]
}

331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
# File 'lib/nanook/wallet.rb', line 331

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

  unless Nanook::UNITS.include?(unit)
    raise ArgumentError.new("Unsupported unit: #{unit}")
  end

  params = { count: limit }
  params[:source] = true if detailed

  response = rpc(:wallet_pending, params)[:blocks]
  response = Nanook::Util.coerce_empty_string_to_type(response, Hash)

  return response unless detailed

  # 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 = amount_and_source.merge(block: block.to_s)
      if unit == :nano
        d[:amount] = Nanook::Util.raw_to_NANO(d[:amount])
      end
      d
    end

    [, new_data]
  end

  Hash[x].to_symbolized_hash
end

#receive(block = nil, into:) ⇒ String, 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 hash id 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...")               # => "9AE2311..."
wallet.receive("718CC21...", into: "xrb...") # => "9AE2311..."

384
385
386
387
388
# File 'lib/nanook/wallet.rb', line 384

def receive(block=nil, into:)
  wallet_required!
  validate_wallet_contains_account!(into)
  (into).receive(block)
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:


447
448
449
450
451
452
453
454
455
456
457
458
459
# File 'lib/nanook/wallet.rb', line 447

def restore(seed, accounts:0)
  create

  unless change_seed(seed)
    raise Nanook::Error.new("Unable to set seed for wallet")
  end

  if accounts > 0
    .create(accounts)
  end

  self
end

#unlock(password) ⇒ Boolean

Unlocks a previously locked wallet.

Example:

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

528
529
530
531
# File 'lib/nanook/wallet.rb', line 528

def unlock(password)
  wallet_required!
  rpc(:password_enter, password: password)[:valid] == 1
end