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