class Nanook
# The Nanook::Account class contains methods to discover
# publicly-available information about accounts on the nano network.
#
# === Initializing
#
# Initialize this class through the convenient {Nanook#account} method:
#
# nanook = Nanook.new
# account = nanook.account("xrb_...")
#
# Or compose the longhand way like this:
#
# rpc_conn = Nanook::Rpc.new
# account = Nanook::Account.new(rpc_conn, "xrb_...")
class Account
def initialize(rpc, account)
@rpc = rpc
@account = account
end
# Information about this accounts that have set this account as their representative.
#
# === Example:
#
# account.delegators
#
# Example response:
#
# {
# :xrb_13bqhi1cdqq8yb9szneoc38qk899d58i5rcrgdk5mkdm86hekpoez3zxw5sd=>500000000000000000000000000000000000,
# :xrb_17k6ug685154an8gri9whhe5kb5z1mf5w6y39gokc1657sh95fegm8ht1zpn=>961647970820730000000000000000000000
# }
#
# @param unit (see #balance)
# @return [Hash{Symbol=>Integer}] account ids which delegate to this account, and their account balance
def delegators(unit: Nanook.default_unit)
unless Nanook::UNITS.include?(unit)
raise ArgumentError.new("Unsupported unit: #{unit}")
end
response = rpc(:delegators)[:delegators]
return response if unit == :raw
r = response.map do |account_id, balance|
balance = Nanook::Util.raw_to_NANO(balance)
[account_id, balance]
end
Hash[r].to_symbolized_hash
end
# Returns true if the account has an open block.
#
# An open block gets published when an account receives a payment
# for the first time.
#
# The reliability of this check depends on the node host having
# synchronized itself with most of the blocks on the nano network,
# otherwise you may get +false+ when the account does exist.
# You can check if a node's synchronization is particular low
# using {Nanook::Node#sync_progress}.
#
# ==== Example:
#
# account.exists? # => true
# # or
# account.open? # => true
#
# @return [Boolean] Indicates if this account has an open block
def exists?
response = rpc(:account_info)
!response.empty? && !response[:open_block].nil?
end
alias_method :open?, :exists?
# An account's history of send and receive payments.
#
# ==== Example:
#
# account.history
#
# Example response:
#
# [
# {
# type: "send",
# account: "xrb_1kdc5u48j3hr5r7eof9iao47szqh81ndqgq5e5hrsn1g9a3sa4hkkcotn3uq",
# amount: 2,
# hash: "2C3C570EA8898443C0FD04A1C385A3E3A8C985AD792635FCDCEBB30ADF6A0570"
# }
# ]
#
# @param limit [Integer] maximum number of history items to return
# @param unit (see #balance)
# @return [ArrayString}>] the history of send and receive payments for this account
def history(limit: 1000, unit: Nanook.default_unit)
unless Nanook::UNITS.include?(unit)
raise ArgumentError.new("Unsupported unit: #{unit}")
end
response = rpc(:account_history, count: limit)[:history]
if unit == :raw
return response
end
response.map! do |history|
history[:amount] = Nanook::Util.raw_to_NANO(history[:amount])
history
end
end
# The last modified time of the account in the time zone of
# your nano node (usually UTC).
#
# ==== Example:
#
# account.last_modified_at # => Time
#
# @return [Time] last modified time of the account in the time zone of
# your nano node (usually UTC).
def last_modified_at
response = rpc(:account_info)
Time.at(response[:modified_timestamp])
end
# The public key of the account.
#
# ==== Example:
#
# account.public_key # => "3068BB1..."
#
# @return [String] public key of the account
def public_key
rpc(:account_key)[:key]
end
# The representative account id for the account.
# Representatives are accounts that cast votes in the case of a
# fork in the network.
#
# ==== Example:
#
# account.representative # => "xrb_3pc..."
#
# @return [String] Representative account of the account
def representative
rpc(:account_representative)[:representative]
end
# The account's balance, including pending (unreceived payments).
# To receive a pending amount see {WalletAccount#receive}.
#
# ==== Examples:
#
# account.balance
#
# Example response:
#
# {
# balance: 2,
# pending: 1.1
# }
#
# Asking for the balance to be returned in raw instead of NANO:
#
# account.balance(unit: :raw)
#
# Example response:
#
# {
# balance: 2000000000000000000000000000000,
# pending: 1100000000000000000000000000000
# }
#
# @param unit [Symbol] default is {Nanook.default_unit}.
# Must be one of {Nanook::UNITS}.
# Represents the unit that the balances will be returned in.
# Note: this method interprets
# +:nano+ as NANO, which is technically Mnano.
# See {https://nano.org/en/faq#what-are-nano-units- What are Nano's Units}
#
# @raise ArgumentError if an invalid +unit+ was given.
# @return [Hash{Symbol=>Integer|Float}]
def balance(unit: Nanook.default_unit)
unless Nanook::UNITS.include?(unit)
raise ArgumentError.new("Unsupported unit: #{unit}")
end
rpc(:account_balance).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
# @return [Integer] number of blocks for this account
def block_count
rpc(:account_block_count)[:block_count]
end
# The id of the account.
#
# ==== Example:
#
# account.id # => "xrb_16u..."
#
# @return [String] the id of the account
def id
@account
end
# Information about the account.
#
# ==== Examples:
#
# account.info
#
# Example response:
#
# {
# id: "xrb_16u1uufyoig8777y6r8iqjtrw8sg8maqrm36zzcm95jmbd9i9aj5i8abr8u5",
# balance: 11.439597000000001,
# block_count: 4,
# frontier: "2C3C570EA8898443C0FD04A1C385A3E3A8C985AD792635FCDCEBB30ADF6A0570",
# modified_timestamp: 1520500357,
# open_block: "C82376314C387080A753871A32AD70F4168080C317C5E67356F0A62EB5F34FF9",
# representative_block: "C82376314C387080A753871A32AD70F4168080C317C5E67356F0A62EB5F34FF9"
# }
#
# Asking for more detail to be returned:
#
# account.info(detailed: true)
#
# Example response:
#
# {
# id: "xrb_16u1uufyoig8777y6r8iqjtrw8sg8maqrm36zzcm95jmbd9i9aj5i8abr8u5",
# balance: 11.439597000000001,
# block_count: 4,
# frontier: "2C3C570EA8898443C0FD04A1C385A3E3A8C985AD792635FCDCEBB30ADF6A0570",
# modified_timestamp: 1520500357,
# open_block: "C82376314C387080A753871A32AD70F4168080C317C5E67356F0A62EB5F34FF9",
# pending: 0,
# public_key: "A82C906460046D230D7D37C6663723DC3EFCECC4B3254EBF45294B66746F4FEF",
# representative: "xrb_3pczxuorp48td8645bs3m6c3xotxd3idskrenmi65rbrga5zmkemzhwkaznh",
# representative_block: "C82376314C387080A753871A32AD70F4168080C317C5E67356F0A62EB5F34FF9",
# weight: 0
# }
#
# @param detailed [Boolean] (default is false). When +true+, four
# additional calls are made to the RPC to return more information
# @param unit (see #balance)
# @return [Hash{Symbol=>String|Integer|Float}] information about the account containing:
# [+id+] The account id
# [+frontier+] The latest block hash
# [+open_block+] The first block in every account's blockchain. When this block was published the account was officially open
# [+representative_block+] The block that named the representative for the account
# [+balance+] Amount in either NANO or raw (depending on the unit: argument)
# [+last_modified+] Unix timestamp
# [+block_count+] Number of blocks in the account's blockchain
#
# When detailed: true is passed as an argument, this method
# makes four additional calls to the RPC to return more information
# about an account:
#
# [+weight+] See {#weight}
# [+pending+] See {#balance}
# [+representative+] See {#representative}
# [+public_key+] See {#public_key}
def info(detailed: false, unit: Nanook.default_unit)
unless Nanook::UNITS.include?(unit)
raise ArgumentError.new("Unsupported unit: #{unit}")
end
response = rpc(:account_info)
response.merge!(id: @account)
if unit == :nano
response[:balance] = Nanook::Util.raw_to_NANO(response[:balance])
end
# Return the response if we don't need any more info
return response unless detailed
# Otherwise make additional calls
response.merge!({
weight: weight,
pending: balance(unit: unit)[:pending],
representative: representative,
public_key: public_key
})
# Sort this new hash by keys
Hash[response.sort].to_symbolized_hash
end
def inspect
"#{self.class.name}(id: \"#{id}\", object_id: \"#{"0x00%x" % (object_id << 1)}\")"
end
# Information about the given account as well as other
# accounts up the ledger. The number of accounts returned is determined
# by the limit: argument.
#
# ==== Example:
#
# account.ledger(limit: 2)
#
# Example response:
#
# {
# :xrb_3c3ek3k8135f6e8qtfy8eruk9q3yzmpebes7btzncccdest8ymzhjmnr196j=>{
# :frontier=>"2C3C570EA8898443C0FD04A1C385A3E3A8C985AD792635FCDCEBB30ADF6A0570",
# :open_block=>"C82376314C387080A753871A32AD70F4168080C317C5E67356F0A62EB5F34FF9",
# :representative_block=>"C82376314C387080A753871A32AD70F4168080C317C5E67356F0A62EB5F34FF9",
# :balance=>11439597000000000000000000000000,
# :modified_timestamp=>1520500357,
# :block_count=>4
# },
# :xrb_3c3ettq59kijuuad5fnaq35itc9schtr4r7r6rjhmwjbairowzq3wi5ap7h8=>{ ... }
# }
#
# @param [Integer] limit number of accounts to return in the ledger (default is 1)
# @param [Time] modified_since return only accounts modified in the local database after this time
# @param unit (see #balance)
# @return [Hash{Symbol=>String|Integer}]
def ledger(limit: 1, modified_since:nil, unit: Nanook.default_unit)
unless Nanook::UNITS.include?(unit)
raise ArgumentError.new("Unsupported unit: #{unit}")
end
params = { count: limit }
unless modified_since.nil?
params[:modified_since] = modified_since.to_i
end
response = rpc(:ledger, params)[:accounts]
return response if unit == :raw
r = response.map do |account_id, l|
l[:balance] = Nanook::Util.raw_to_NANO(l[:balance])
[account_id, l]
end
Hash[r].to_symbolized_hash
end
# Information about pending blocks (payments) that are
# waiting to be received by the account.
#
# See also the {Nanook::WalletAccount#receive} method for how to
# receive a pending payment.
#
# The default response is an Array of block ids.
#
# With the +detailed:+ argument, the method returns an Array of Hashes,
# which contain the source account id, amount pending and block id.
#
# ==== Examples:
#
# account.pending # => ["000D1BA..."]
#
# Asking for more detail to be returned:
#
# account.pending(detailed: true)
#
# Example response:
#
# [
# {
# block: "000D1BAEC8EC208142C99059B393051BAC8380F9B5A2E6B2489A277D81789F3F",
# amount: 6,
# source: "xrb_3dcfozsmekr1tr9skf1oa5wbgmxt81qepfdnt7zicq5x3hk65fg4fqj58mbr"
# },
# { ... }
# ]
#
# @param limit [Integer] number of pending blocks to return (default is 1000)
# @param detailed [Boolean]return a more complex Hash of pending block information (default is +false+)
# @param unit (see #balance)
#
# @return [Array]
# @return [ArrayString|Integer}>]
def pending(limit: 1000, detailed: false, unit: Nanook.default_unit)
unless Nanook::UNITS.include?(unit)
raise ArgumentError.new("Unsupported unit: #{unit}")
end
params = { count: limit }
params[:source] = true if detailed
response = rpc(:pending, params)[:blocks]
response = Nanook::Util.coerce_empty_string_to_type(response, (detailed ? Hash : Array))
return response unless detailed
response.map do |key, val|
p = val.merge(block: key.to_s)
if unit == :nano
p[:amount] = Nanook::Util.raw_to_NANO(p[:amount])
end
p
end
end
# The account's weight.
#
# Weight is determined by the account's balance, and represents
# the voting weight that account has on the network. Only accounts
# with greater than 256 weight can vote.
#
# ==== Example:
#
# account.weight # => 0
#
# @return [Integer] the account's weight
def weight
rpc(:account_weight)[:weight]
end
private
def rpc(action, params={})
p = @account.nil? ? {} : { account: @account }
@rpc.call(action, p.merge(params))
end
end
end