banano 0.1.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.
- checksums.yaml +7 -0
- data/CHANGELOG.md +2 -0
- data/LICENSE.txt +21 -0
- data/README.md +183 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/lib/banano.rb +46 -0
- data/lib/banano/account.rb +202 -0
- data/lib/banano/client.rb +33 -0
- data/lib/banano/error.rb +7 -0
- data/lib/banano/key.rb +47 -0
- data/lib/banano/node.rb +115 -0
- data/lib/banano/unit.rb +41 -0
- data/lib/banano/util.rb +25 -0
- data/lib/banano/version.rb +5 -0
- data/lib/banano/wallet.rb +430 -0
- data/lib/banano/wallet_account.rb +227 -0
- metadata +161 -0
@@ -0,0 +1,33 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'faraday'
|
4
|
+
require 'faraday_middleware'
|
5
|
+
|
6
|
+
module Banano
|
7
|
+
class Client
|
8
|
+
LOCAL_ENDPOINT = 'http://localhost:7072'
|
9
|
+
DEFAULT_TIMEOUT = 30
|
10
|
+
|
11
|
+
attr_accessor :uri, :timeout
|
12
|
+
|
13
|
+
def initialize(uri: LOCAL_ENDPOINT, timeout: DEFAULT_TIMEOUT)
|
14
|
+
@conn = Faraday.new(uri) do |builder|
|
15
|
+
builder.adapter Faraday.default_adapter
|
16
|
+
builder.request :url_encoded
|
17
|
+
builder.options[:open_timeout] = 5
|
18
|
+
builder.options[:timeout] = timeout
|
19
|
+
builder.headers['Content-Type'] = 'application/json'
|
20
|
+
builder.headers['User-Agent'] = 'Banano RPC Client'
|
21
|
+
builder.response :json, content_type: 'application/json'
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def rpc_call(action:, params: {})
|
26
|
+
data = {action: action}.merge(params)
|
27
|
+
response = @conn.post do |req|
|
28
|
+
req.body = JSON.dump(data)
|
29
|
+
end
|
30
|
+
Util.symbolize_keys(response.body)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
data/lib/banano/error.rb
ADDED
data/lib/banano/key.rb
ADDED
@@ -0,0 +1,47 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Banano
|
4
|
+
class Key
|
5
|
+
def initialize(node:, key: nil)
|
6
|
+
@node = node
|
7
|
+
@key = key
|
8
|
+
end
|
9
|
+
|
10
|
+
def generate(seed: nil, index: nil)
|
11
|
+
if seed.nil? && index.nil?
|
12
|
+
rpc(action: :key_create)
|
13
|
+
elsif !seed.nil? && !index.nil?
|
14
|
+
rpc(action: :deterministic_key, params: {seed: seed, index: index})
|
15
|
+
else
|
16
|
+
raise ArgumentError, "Method must be called with either seed AND index params"
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
# Derive public key and account from private key
|
21
|
+
def expand
|
22
|
+
return {} if @key.nil?
|
23
|
+
|
24
|
+
rpc(action: :key_expand, params: {key: @key})
|
25
|
+
end
|
26
|
+
|
27
|
+
def id
|
28
|
+
@key
|
29
|
+
end
|
30
|
+
|
31
|
+
def info
|
32
|
+
key_required!
|
33
|
+
rpc(action: :key_expand)
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
def rpc(action:, params: {})
|
39
|
+
p = @key.nil? ? {} : {key: @key}
|
40
|
+
@node.rpc(action: action, params: p.merge(params))
|
41
|
+
end
|
42
|
+
|
43
|
+
def key_required!
|
44
|
+
raise ArgumentError, "Key must be present" if @key.nil?
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
data/lib/banano/node.rb
ADDED
@@ -0,0 +1,115 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Banano
|
4
|
+
class Node
|
5
|
+
attr_reader :uri, :timeout
|
6
|
+
|
7
|
+
def initialize(uri: Client::LOCAL_ENDPOINT, timeout: Client::DEFAULT_TIMEOUT)
|
8
|
+
@client = Client.new(uri: uri, timeout: timeout)
|
9
|
+
end
|
10
|
+
|
11
|
+
def rpc(action:, params: {})
|
12
|
+
@client.rpc_call(action: action, params: params)
|
13
|
+
end
|
14
|
+
|
15
|
+
# The number of accounts in the nano ledger--essentially all
|
16
|
+
# accounts with _open_ blocks. An _open_ block
|
17
|
+
# is the type of block written to the nano ledger when an account
|
18
|
+
# receives its first payment (see {Nanook::WalletAccount#receive}). All accounts
|
19
|
+
# that respond +true+ to {Nanook::Account#exists?} have open blocks in the ledger.
|
20
|
+
#
|
21
|
+
# @return [Integer] number of accounts with _open_ blocks.
|
22
|
+
def account_count
|
23
|
+
rpc(action: :frontier_count)[:count]
|
24
|
+
end
|
25
|
+
alias frontier_count account_count
|
26
|
+
|
27
|
+
# The count of all blocks downloaded to the node, and
|
28
|
+
# blocks still to be synchronized by the node.
|
29
|
+
#
|
30
|
+
# @return [Hash{Symbol=>Integer}] number of blocks and unchecked
|
31
|
+
# synchronizing blocks
|
32
|
+
def block_count
|
33
|
+
rpc(action: :block_count)
|
34
|
+
end
|
35
|
+
|
36
|
+
# The count of all known blocks by their type.
|
37
|
+
#
|
38
|
+
# @return [Hash{Symbol=>Integer}] number of blocks by type
|
39
|
+
def block_count_by_type
|
40
|
+
rpc(action: :block_count_type)
|
41
|
+
end
|
42
|
+
alias block_count_type block_count_by_type
|
43
|
+
|
44
|
+
# TODO: add bootstrap methods
|
45
|
+
|
46
|
+
# @return [Hash{Symbol=>String}] information about the node peers
|
47
|
+
def peers
|
48
|
+
rpc(action: :peers)[:peers]
|
49
|
+
end
|
50
|
+
|
51
|
+
# All representatives and their voting weight.
|
52
|
+
#
|
53
|
+
# @param raw [Boolean] if true return raw balances, else banano units
|
54
|
+
# @return [Hash{Symbol=>Integer}] known representatives and their voting weight
|
55
|
+
def representatives(raw = true)
|
56
|
+
response = rpc(action: :representatives)[:representatives]
|
57
|
+
return response if raw == true
|
58
|
+
|
59
|
+
r = response.map do |address, balance|
|
60
|
+
[address.to_s, Banano::Unit.raw_to_ban(balance).to_f]
|
61
|
+
end
|
62
|
+
Hash[r]
|
63
|
+
end
|
64
|
+
|
65
|
+
# All online representatives that have voted recently. Note, due to the
|
66
|
+
# design of the nano RPC, this method cannot return the voting weight
|
67
|
+
# of the representatives.
|
68
|
+
#
|
69
|
+
# ==== Example:
|
70
|
+
#
|
71
|
+
# node.representatives_online # => ["ban_111...", "ban_311..."]
|
72
|
+
#
|
73
|
+
# @return [Array<String>] array of representative account ids
|
74
|
+
def representatives_online
|
75
|
+
rpc(action: :representatives_online)[:representatives]
|
76
|
+
end
|
77
|
+
alias reps_online representatives_online
|
78
|
+
|
79
|
+
# @param limit [Integer] number of synchronizing blocks to return
|
80
|
+
# @return [Hash{Symbol=>String}] information about the synchronizing blocks for this node
|
81
|
+
def synchronizing_blocks(limit: 1000)
|
82
|
+
response = rpc(action: :unchecked, params: {count: limit})[:blocks]
|
83
|
+
# response = response.map do |block, info|
|
84
|
+
# [block, JSON.parse(info).to_symbolized_hash]
|
85
|
+
# end
|
86
|
+
# Hash[response.sort].to_symbolized_hash
|
87
|
+
response
|
88
|
+
end
|
89
|
+
alias unchecked synchronizing_blocks
|
90
|
+
|
91
|
+
# The percentage completeness of the synchronization process for
|
92
|
+
# your node as it downloads the nano ledger. Note, it's normal for
|
93
|
+
# your progress to not ever reach 100. The closer to 100, the more
|
94
|
+
# complete your node's data is, and so the query methods in this class
|
95
|
+
# become more reliable.
|
96
|
+
#
|
97
|
+
# @return [Float] the percentage completeness of the synchronization
|
98
|
+
# process for your node
|
99
|
+
def sync_progress
|
100
|
+
response = block_count
|
101
|
+
|
102
|
+
count = response[:count].to_i
|
103
|
+
unchecked = response[:unchecked].to_i
|
104
|
+
total = count + unchecked
|
105
|
+
|
106
|
+
count.to_f * 100 / total.to_f
|
107
|
+
end
|
108
|
+
|
109
|
+
# @return [Hash{Symbol=>Integer|String}] version information for this node
|
110
|
+
def version
|
111
|
+
rpc(action: :version)
|
112
|
+
end
|
113
|
+
alias info version
|
114
|
+
end
|
115
|
+
end
|
data/lib/banano/unit.rb
ADDED
@@ -0,0 +1,41 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'bigdecimal'
|
4
|
+
|
5
|
+
module Banano
|
6
|
+
class Unit
|
7
|
+
# Constant used to convert back and forth between raw and banano
|
8
|
+
STEP = BigDecimal(10)**29
|
9
|
+
TOTAL = (BigDecimal(2)**128 - 1).to_i
|
10
|
+
|
11
|
+
# Converts an amount of banano to an amount of raw.
|
12
|
+
#
|
13
|
+
# @param banano [Float|Integer] amount in banano
|
14
|
+
# @return [Integer] amount in raw
|
15
|
+
def self.ban_to_raw(banano)
|
16
|
+
return 0 unless banano.is_a?(Numeric) && banano > 0
|
17
|
+
|
18
|
+
result = (banano * STEP).to_i
|
19
|
+
return 0 if result > TOTAL
|
20
|
+
|
21
|
+
result
|
22
|
+
end
|
23
|
+
|
24
|
+
# Converts an amount of raw to an amount of banano
|
25
|
+
#
|
26
|
+
# @param raw [BigDecimal|String] amount in raw
|
27
|
+
# @return [Float|Integer] amount in banano
|
28
|
+
def self.raw_to_ban(raw)
|
29
|
+
return 0 unless raw.is_a?(BigDecimal) || raw.is_a?(String)
|
30
|
+
|
31
|
+
begin
|
32
|
+
value = raw.is_a?(String) ? BigDecimal(raw) : raw
|
33
|
+
return 0 if value < 1.0 || value > TOTAL
|
34
|
+
|
35
|
+
value / STEP
|
36
|
+
rescue ArgumentError
|
37
|
+
0
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
data/lib/banano/util.rb
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Banano
|
4
|
+
class Util
|
5
|
+
class << self
|
6
|
+
def symbolize_keys(hash)
|
7
|
+
return {} if hash.empty?
|
8
|
+
|
9
|
+
converted = hash.is_a?(String) ? JSON.parse(hash) : hash
|
10
|
+
converted.inject({}) do |result, (key, value)|
|
11
|
+
new_key = case key
|
12
|
+
when String then key.to_sym
|
13
|
+
else key
|
14
|
+
end
|
15
|
+
new_value = case value
|
16
|
+
when Hash then symbolize_keys(value)
|
17
|
+
else value
|
18
|
+
end
|
19
|
+
result[new_key] = new_value
|
20
|
+
result
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,430 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# The <tt>Banano::Wallet</tt> class lets you manage your banano wallets,
|
4
|
+
# as well as some account-specific things like making and receiving payments.
|
5
|
+
#
|
6
|
+
# === Wallet seeds vs ids
|
7
|
+
#
|
8
|
+
# Your wallets each have an id as well as a seed. Both are 32-byte uppercase hex
|
9
|
+
# strings that look like this:
|
10
|
+
#
|
11
|
+
# 000D1BAEC8EC208142C99059B393051BAC8380F9B5A2E6B2489A277D81789F3F
|
12
|
+
#
|
13
|
+
# This class uses wallet _ids_ to identify your wallet. A wallet id only
|
14
|
+
# exists locally on the banano node that it was created on. The person
|
15
|
+
# who knows this id can only perform all read and write actions against
|
16
|
+
# the wallet and all accounts inside the wallet from the same banano node
|
17
|
+
# that it was created on. This makes wallet ids fairly safe to use as a
|
18
|
+
# person needs to know your wallet id as well as have access to run
|
19
|
+
# RPC commands against your banano node to be able to control your accounts.
|
20
|
+
#
|
21
|
+
# A _seed_ on the other hand can be used to link any wallet to another
|
22
|
+
# wallet's accounts, from anywhere in the banano network. This happens
|
23
|
+
# by setting a wallet's seed to be the same as a previous wallet's seed.
|
24
|
+
# When a wallet has the same seed as another wallet, any accounts
|
25
|
+
# created in the second wallet will be the same accounts as those that were
|
26
|
+
# created in the previous wallet, and the new wallet's owner will
|
27
|
+
# also gain ownership of the previous wallet's accounts. Note, that the
|
28
|
+
# two wallets will have different ids, but the same seed.
|
29
|
+
#
|
30
|
+
|
31
|
+
module Banano
|
32
|
+
class Wallet
|
33
|
+
attr_reader :node
|
34
|
+
|
35
|
+
def initialize(node:, wallet: nil)
|
36
|
+
@node = node
|
37
|
+
@wallet = wallet
|
38
|
+
end
|
39
|
+
|
40
|
+
# Changes a wallet's seed.
|
41
|
+
#
|
42
|
+
# It's recommended to only change the seed of a wallet that contains
|
43
|
+
# no accounts.
|
44
|
+
#
|
45
|
+
# ==== Example:
|
46
|
+
#
|
47
|
+
# wallet.change_seed("000D1BA...") # => true
|
48
|
+
#
|
49
|
+
# @param seed [String] the seed to change to.
|
50
|
+
# @return [Boolean] indicating whether the change was successful.
|
51
|
+
def change_seed(seed)
|
52
|
+
wallet_required!
|
53
|
+
rpc(action: :wallet_change_seed, params: {seed: seed}).key?(:success)
|
54
|
+
end
|
55
|
+
|
56
|
+
# Returns the given account in the wallet as a {Banano::WalletAccount} instance
|
57
|
+
# to let you start working with it.
|
58
|
+
#
|
59
|
+
# Call with no +account+ argument if you wish to create a new account
|
60
|
+
# in the wallet, like this:
|
61
|
+
#
|
62
|
+
# wallet.account.create # => Banano::WalletAccount
|
63
|
+
#
|
64
|
+
# See {Banano::WalletAccount} for all the methods you can call on the
|
65
|
+
# account object returned.
|
66
|
+
#
|
67
|
+
# ==== Examples:
|
68
|
+
#
|
69
|
+
# wallet.account("nano_...") # => Banano::WalletAccount
|
70
|
+
# wallet.account.create # => Banano::WalletAccount
|
71
|
+
#
|
72
|
+
# @param [String] account optional String of an account (starting with
|
73
|
+
# <tt>"xrb..."</tt>) to start working with. Must be an account within
|
74
|
+
# the wallet. When no account is given, the instance returned only
|
75
|
+
# allows you to call +create+ on it, to create a new account.
|
76
|
+
# @raise [ArgumentError] if the wallet does no contain the account
|
77
|
+
# @return [Banano::WalletAccount]
|
78
|
+
def account(account = nil)
|
79
|
+
Banano::WalletAccount.new(node: @node, wallet: @wallet, account: account)
|
80
|
+
end
|
81
|
+
|
82
|
+
# Array of {Banano::WalletAccount} instances of accounts in the wallet.
|
83
|
+
#
|
84
|
+
# ==== Example:
|
85
|
+
#
|
86
|
+
# wallet.accounts # => [Banano::WalletAccount, Banano::WalletAccount...]
|
87
|
+
#
|
88
|
+
# @return [Array<Banano::WalletAccount>] all accounts in the wallet
|
89
|
+
def accounts
|
90
|
+
wallet_required!
|
91
|
+
rpc(action: :account_list)[:accounts]
|
92
|
+
end
|
93
|
+
|
94
|
+
# Will return +true+ if the account exists in the wallet.
|
95
|
+
#
|
96
|
+
# ==== Example:
|
97
|
+
# wallet.contains?("ban_1...") # => true
|
98
|
+
#
|
99
|
+
# @param account [String] id (will start with <tt>"ban_..."</tt>)
|
100
|
+
# @return [Boolean] indicating if the wallet contains the given account
|
101
|
+
# TODO: account address validation - Maybe Banano::Address ....
|
102
|
+
def contains?(account)
|
103
|
+
wallet_required!
|
104
|
+
response = rpc(action: :wallet_contains, params: {account: account})
|
105
|
+
!response.empty? && response[:exists] == '1'
|
106
|
+
end
|
107
|
+
|
108
|
+
# Creates a new wallet.
|
109
|
+
#
|
110
|
+
# The wallet will be created only on this node. It's important that
|
111
|
+
# if you intend to add funds to accounts in this wallet that you
|
112
|
+
# backup the wallet *seed* in order to restore the wallet in future.
|
113
|
+
#
|
114
|
+
# ==== Example:
|
115
|
+
# Banano::Wallet.new.create # => Banano::Wallet
|
116
|
+
#
|
117
|
+
# @return [Banano::Wallet]
|
118
|
+
def create
|
119
|
+
@wallet = rpc(action: :wallet_create)[:wallet]
|
120
|
+
self
|
121
|
+
end
|
122
|
+
|
123
|
+
# Destroys the wallet.
|
124
|
+
#
|
125
|
+
# ==== Example:
|
126
|
+
#
|
127
|
+
# wallet.destroy # => true
|
128
|
+
#
|
129
|
+
# @return [Boolean] indicating success of the action
|
130
|
+
def destroy
|
131
|
+
wallet_required!
|
132
|
+
rpc(action: :wallet_destroy)
|
133
|
+
@wallet = nil
|
134
|
+
true
|
135
|
+
end
|
136
|
+
|
137
|
+
# Generates a String containing a JSON representation of your wallet.
|
138
|
+
#
|
139
|
+
def export
|
140
|
+
wallet_required!
|
141
|
+
rpc(action: :wallet_export)[:json]
|
142
|
+
end
|
143
|
+
|
144
|
+
# The default representative account id for the wallet. This is the
|
145
|
+
# representative that all new accounts created in this wallet will have.
|
146
|
+
#
|
147
|
+
# Changing the default representative for a wallet does not change
|
148
|
+
# the representatives for any accounts that have been created.
|
149
|
+
#
|
150
|
+
# ==== Example:
|
151
|
+
#
|
152
|
+
# wallet.default_representative # => "ban_3pc..."
|
153
|
+
#
|
154
|
+
# @return [String] Representative account of the account
|
155
|
+
def default_representative
|
156
|
+
rpc(action: :wallet_representative)[:representative]
|
157
|
+
end
|
158
|
+
alias representative default_representative
|
159
|
+
|
160
|
+
# Sets the default representative for the wallet. A wallet's default
|
161
|
+
# representative is the representative all new accounts created in
|
162
|
+
# the wallet will have. Changing the default representative for a
|
163
|
+
# wallet does not change the representatives for existing accounts
|
164
|
+
# in the wallet.
|
165
|
+
#
|
166
|
+
# ==== Example:
|
167
|
+
#
|
168
|
+
# wallet.change_default_representative("ban_...") # => "ban_..."
|
169
|
+
#
|
170
|
+
# @param [String] representative the id of the representative account
|
171
|
+
# to set as this account's representative
|
172
|
+
# @return [String] the representative account id
|
173
|
+
# @raise [ArgumentError] if the representative account does not exist
|
174
|
+
# @raise [Banano::Error] if setting the representative fails
|
175
|
+
def change_default_representative(representative)
|
176
|
+
unless Banano::Account.new(node: @node, address: representative).exists?
|
177
|
+
raise ArgumentError, "Representative account does not exist: #{representative}"
|
178
|
+
end
|
179
|
+
|
180
|
+
if rpc(action: :wallet_representative_set,
|
181
|
+
params: {representative: representative})[:set] == '1'
|
182
|
+
representative
|
183
|
+
else
|
184
|
+
raise Banano::Error, "Setting the representative failed"
|
185
|
+
end
|
186
|
+
end
|
187
|
+
alias change_representative change_default_representative
|
188
|
+
|
189
|
+
# @return [String] the wallet id
|
190
|
+
def id
|
191
|
+
@wallet
|
192
|
+
end
|
193
|
+
|
194
|
+
# Information about this wallet and all of its accounts.
|
195
|
+
#
|
196
|
+
# ==== Examples:
|
197
|
+
#
|
198
|
+
# wallet.info
|
199
|
+
#
|
200
|
+
# @param raw [Boolean] if true return raw, else return ban units
|
201
|
+
# @return [Hash{Symbol=>String|Array<Hash{Symbol=>String|Integer|Float}>}]
|
202
|
+
# information about the wallet.
|
203
|
+
def info
|
204
|
+
wallet_required!
|
205
|
+
rpc(action: :wallet_info)
|
206
|
+
end
|
207
|
+
|
208
|
+
# Locks the wallet. A locked wallet cannot pocket pending transactions or make payments.
|
209
|
+
#
|
210
|
+
# ==== Example:
|
211
|
+
#
|
212
|
+
# wallet.lock #=> true
|
213
|
+
#
|
214
|
+
# @return [Boolean] indicates if the wallet was successfully locked
|
215
|
+
def lock
|
216
|
+
wallet_required!
|
217
|
+
response = rpc(action: :wallet_lock)
|
218
|
+
!response.empty? && response[:locked] == '1'
|
219
|
+
end
|
220
|
+
|
221
|
+
# Returns +true+ if the wallet is locked.
|
222
|
+
#
|
223
|
+
# ==== Example:
|
224
|
+
#
|
225
|
+
# wallet.locked? #=> false
|
226
|
+
#
|
227
|
+
# @return [Boolean] indicates if the wallet is locked
|
228
|
+
def locked?
|
229
|
+
wallet_required!
|
230
|
+
response = rpc(action: :wallet_locked)
|
231
|
+
!response.empty? && response[:locked] != '0'
|
232
|
+
end
|
233
|
+
|
234
|
+
# Unlocks a previously locked wallet.
|
235
|
+
#
|
236
|
+
# ==== Example:
|
237
|
+
#
|
238
|
+
# wallet.unlock("new_pass") #=> true
|
239
|
+
#
|
240
|
+
# @return [Boolean] indicates if the unlocking action was successful
|
241
|
+
def unlock(password)
|
242
|
+
wallet_required!
|
243
|
+
rpc(action: :password_enter, params: {password: password})[:valid] == '1'
|
244
|
+
end
|
245
|
+
|
246
|
+
# Changes the password for a wallet.
|
247
|
+
#
|
248
|
+
# ==== Example:
|
249
|
+
#
|
250
|
+
# wallet.change_password("new_pass") #=> true
|
251
|
+
# @return [Boolean] indicates if the action was successful
|
252
|
+
def change_password(password)
|
253
|
+
wallet_required!
|
254
|
+
rpc(action: :password_change, params: {password: password})[:changed] == '1'
|
255
|
+
end
|
256
|
+
|
257
|
+
# Balance of all accounts in the wallet, optionally breaking the balances down by account.
|
258
|
+
#
|
259
|
+
# ==== Examples:
|
260
|
+
# wallet.balance
|
261
|
+
#
|
262
|
+
# Example response:
|
263
|
+
#
|
264
|
+
# {
|
265
|
+
# "balance"=>5,
|
266
|
+
# "pending"=>0.001
|
267
|
+
# }
|
268
|
+
#
|
269
|
+
# @param [Boolean] account_break_down (default is +false+). When +true+
|
270
|
+
# the response will contain balances per account.
|
271
|
+
# @param raw [Boolean] raw or banano units
|
272
|
+
#
|
273
|
+
# @return [Hash{Symbol=>Integer|Float|Hash}]
|
274
|
+
def balance(account_break_down: false, raw: true)
|
275
|
+
wallet_required!
|
276
|
+
|
277
|
+
if account_break_down
|
278
|
+
response = rpc(action: :wallet_balances)[:balances].tap do |r|
|
279
|
+
unless raw == true
|
280
|
+
r.each do |account, _|
|
281
|
+
r[account][:balance] = Banano::Unit.raw_to_ban(r[account][:balance]).to_f
|
282
|
+
r[account][:pending] = Banano::Unit.raw_to_ban(r[account][:pending]).to_f
|
283
|
+
end
|
284
|
+
end
|
285
|
+
end
|
286
|
+
return response.collect {|k, v| [k.to_s, v] }.to_h
|
287
|
+
end
|
288
|
+
|
289
|
+
rpc(action: :wallet_balance_total).tap do |r|
|
290
|
+
unless raw == true
|
291
|
+
r[:balance] = Banano::Unit.raw_to_ban(r[:balance]).to_f
|
292
|
+
r[:pending] = Banano::Unit.raw_to_ban(r[:pending]).to_f
|
293
|
+
end
|
294
|
+
end
|
295
|
+
end
|
296
|
+
|
297
|
+
# Makes a payment from an account in your wallet to another account
|
298
|
+
# on the nano network.
|
299
|
+
#
|
300
|
+
# Note, there may be a delay in receiving a response due to Proof of
|
301
|
+
# Work being done. From the {Nano RPC}[https://docs.nano.org/commands/rpc-protocol/#send]:
|
302
|
+
#
|
303
|
+
# <i>Proof of Work is precomputed for one transaction in the
|
304
|
+
# background. If it has been a while since your last transaction it
|
305
|
+
# will send instantly, the next one will need to wait for Proof of
|
306
|
+
# Work to be generated.</i>
|
307
|
+
#
|
308
|
+
# @param from [String] account id of an account in your wallet
|
309
|
+
# @param to (see Banano::WalletAccount#pay)
|
310
|
+
# @param amount (see Banano::WalletAccount#pay)
|
311
|
+
# @param raw [Boolean] raw or banano units
|
312
|
+
# @params id (see Banano::WalletAccount#pay)
|
313
|
+
#
|
314
|
+
# @return (see Banano::WalletAccount#pay)
|
315
|
+
# @raise [Banano::Error] if unsuccessful
|
316
|
+
def pay(from:, to:, amount:, raw: true, id:)
|
317
|
+
wallet_required!
|
318
|
+
validate_wallet_contains_account!(from)
|
319
|
+
# account(from) will return Banano::WalletAccount
|
320
|
+
account(from).pay(to: to, amount: amount, raw: raw, id: id)
|
321
|
+
end
|
322
|
+
|
323
|
+
# Information about pending blocks (payments) that are waiting
|
324
|
+
# to be received by accounts in this wallet.
|
325
|
+
#
|
326
|
+
# See also the {#receive} method of this class for how to receive a pending payment.
|
327
|
+
#
|
328
|
+
# @param limit [Integer] number of accounts with pending payments to return (default is 1000)
|
329
|
+
# @param detailed [Boolean] return complex Hash of pending block info (default is +false+)
|
330
|
+
# @param raw [Boolean] raw or banano units
|
331
|
+
#
|
332
|
+
# ==== Examples:
|
333
|
+
#
|
334
|
+
# wallet.pending
|
335
|
+
#
|
336
|
+
def pending(limit: 1000, detailed: false, raw: true)
|
337
|
+
wallet_required!
|
338
|
+
params = {count: limit}
|
339
|
+
params[:source] = true if detailed
|
340
|
+
|
341
|
+
response = rpc(action: :wallet_pending, params: params)[:blocks]
|
342
|
+
return response unless detailed && !response.empty?
|
343
|
+
|
344
|
+
# Map the RPC response, which is:
|
345
|
+
# account=>block=>[amount|source] into
|
346
|
+
# account=>[block|amount|source]
|
347
|
+
response.map do |account, data|
|
348
|
+
new_data = data.map do |block, amount_and_source|
|
349
|
+
d = amount_and_source.merge(block: block.to_s)
|
350
|
+
d[:amount] = Banano::Unit.raw_to_ban(d[:amount]) unless raw == true
|
351
|
+
d
|
352
|
+
end
|
353
|
+
|
354
|
+
[account, new_data]
|
355
|
+
end
|
356
|
+
end
|
357
|
+
|
358
|
+
# Receives a pending payment into an account in the wallet.
|
359
|
+
#
|
360
|
+
# When called with no +block+ argument, the latest pending payment
|
361
|
+
# for the account will be received.
|
362
|
+
#
|
363
|
+
# Returns a <i>receive</i> block hash id if a receive was successful,
|
364
|
+
# or +false+ if there were no pending payments to receive.
|
365
|
+
#
|
366
|
+
# You can receive a specific pending block if you know it by
|
367
|
+
# passing the block has in as an argument.
|
368
|
+
# ==== Examples:
|
369
|
+
#
|
370
|
+
# wallet.receive(into: "ban_..") # => "9AE2311..."
|
371
|
+
# wallet.receive("718CC21...", into: "ban_..") # => "9AE2311..."
|
372
|
+
#
|
373
|
+
# @param block (see Banano::WalletAccount#receive)
|
374
|
+
# @param into [String] account id of account in your wallet to receive the
|
375
|
+
# payment into
|
376
|
+
#
|
377
|
+
# @return (see Banano::WalletAccount#receive)
|
378
|
+
def receive(block: nil, into:)
|
379
|
+
wallet_required!
|
380
|
+
validate_wallet_contains_account!(into)
|
381
|
+
# account(into) will return Banano::WalletAccount
|
382
|
+
account(into).receive(block)
|
383
|
+
end
|
384
|
+
|
385
|
+
# Restores a previously created wallet by its seed.
|
386
|
+
# A new wallet will be created on your node (with a new wallet id)
|
387
|
+
# and will have its seed set to the given seed.
|
388
|
+
#
|
389
|
+
# ==== Example:
|
390
|
+
#
|
391
|
+
# Banano::Protocol.new.wallet.restore(seed) # => Nanook::Wallet
|
392
|
+
#
|
393
|
+
# @param seed [String] the wallet seed to restore.
|
394
|
+
# @param accounts [Integer] optionally restore the given number of accounts for the wallet.
|
395
|
+
#
|
396
|
+
# @return [Banano::Wallet] a new wallet
|
397
|
+
# @raise [Banano::Error] if unsuccessful
|
398
|
+
def restore(seed:, accounts: 0)
|
399
|
+
create
|
400
|
+
|
401
|
+
raise Nanook::Error, "Unable to set seed for wallet" unless change_seed(seed)
|
402
|
+
|
403
|
+
account.create(accounts) if accounts > 0
|
404
|
+
self
|
405
|
+
end
|
406
|
+
|
407
|
+
private
|
408
|
+
|
409
|
+
def rpc(action:, params: {})
|
410
|
+
p = @wallet.nil? ? {} : {wallet: @wallet}
|
411
|
+
@node.rpc(action: action, params: p.merge(params))
|
412
|
+
end
|
413
|
+
|
414
|
+
def wallet_required!
|
415
|
+
raise ArgumentError, "Wallet must be present" if @wallet.nil?
|
416
|
+
end
|
417
|
+
|
418
|
+
def validate_wallet_contains_account!(account)
|
419
|
+
@known_valid_accounts ||= []
|
420
|
+
return true if @known_valid_accounts.include?(account)
|
421
|
+
|
422
|
+
if contains?(account)
|
423
|
+
@known_valid_accounts << account
|
424
|
+
else
|
425
|
+
raise ArgumentError,
|
426
|
+
"Account does not exist in wallet. Account: #{account}, wallet: #{@wallet}"
|
427
|
+
end
|
428
|
+
end
|
429
|
+
end
|
430
|
+
end
|