zold 0.0.8 → 0.1
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 +4 -4
- data/.github/ISSUE_TEMPLATE.md +12 -0
- data/.github/PULL_REQUEST_TEMPLATE.md +11 -0
- data/.rubocop.yml +9 -1
- data/.simplecov +2 -2
- data/.travis.yml +1 -1
- data/Gemfile +1 -1
- data/LICENSE.txt +1 -1
- data/Procfile +1 -1
- data/README.md +208 -101
- data/Rakefile +2 -1
- data/bin/zold +135 -54
- data/features/cli.feature +1 -1
- data/features/step_definitions/steps.rb +1 -1
- data/features/support/env.rb +1 -1
- data/fixtures/scripts/push-and-pull.sh +35 -0
- data/lib/zold.rb +2 -2
- data/lib/zold/amount.rb +10 -2
- data/lib/zold/commands/{send.rb → clean.rb} +16 -16
- data/lib/zold/commands/create.rb +7 -5
- data/lib/zold/commands/diff.rb +59 -0
- data/lib/zold/commands/fetch.rb +74 -0
- data/lib/zold/commands/{pull.rb → list.rb} +11 -17
- data/lib/zold/commands/merge.rb +50 -0
- data/lib/zold/commands/node.rb +94 -0
- data/lib/zold/commands/pay.rb +58 -0
- data/lib/zold/commands/{check.rb → propagate.rb} +19 -20
- data/lib/zold/commands/push.rb +12 -12
- data/lib/zold/commands/remote.rb +115 -0
- data/lib/zold/commands/{balance.rb → show.rb} +11 -7
- data/lib/zold/copies.rb +126 -0
- data/lib/zold/http.rb +70 -0
- data/lib/zold/id.rb +8 -3
- data/lib/zold/key.rb +2 -2
- data/lib/zold/log.rb +51 -2
- data/lib/zold/node/farm.rb +81 -0
- data/lib/zold/node/front.rb +94 -46
- data/lib/zold/patch.rb +58 -0
- data/lib/zold/remotes.rb +106 -0
- data/lib/zold/score.rb +101 -0
- data/lib/zold/signature.rb +48 -0
- data/lib/zold/version.rb +3 -3
- data/lib/zold/wallet.rb +87 -55
- data/lib/zold/wallets.rb +13 -6
- data/resources/remotes +1 -0
- data/test/commands/test_clean.rb +41 -0
- data/test/commands/test_create.rb +2 -2
- data/test/commands/test_diff.rb +61 -0
- data/test/commands/test_fetch.rb +65 -0
- data/test/commands/test_list.rb +42 -0
- data/test/commands/test_merge.rb +62 -0
- data/test/commands/test_node.rb +56 -0
- data/test/commands/{test_send.rb → test_pay.rb} +10 -11
- data/test/commands/test_remote.rb +60 -0
- data/test/commands/{test_balance.rb → test_show.rb} +6 -8
- data/test/node/fake_node.rb +73 -0
- data/test/node/test_farm.rb +34 -0
- data/test/node/test_front.rb +26 -57
- data/test/test__helper.rb +1 -1
- data/test/test_amount.rb +10 -2
- data/test/test_copies.rb +73 -0
- data/test/test_http.rb +42 -0
- data/test/test_id.rb +2 -2
- data/test/test_key.rb +10 -10
- data/test/test_patch.rb +59 -0
- data/test/test_remotes.rb +72 -0
- data/test/test_score.rb +79 -0
- data/test/test_signature.rb +45 -0
- data/test/test_wallet.rb +18 -35
- data/test/test_wallets.rb +14 -3
- data/test/test_zold.rb +52 -5
- data/zold.gemspec +5 -3
- metadata +92 -21
- data/CONTRIBUTING.md +0 -19
- data/views/index.haml +0 -6
- data/views/layout.haml +0 -26
- data/views/not_found.haml +0 -3
data/Rakefile
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
# Copyright (c) 2018
|
1
|
+
# Copyright (c) 2018 Yegor Bugayenko
|
2
2
|
#
|
3
3
|
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
4
4
|
# of this software and associated documentation files (the 'Software'), to deal
|
@@ -40,6 +40,7 @@ Rake::TestTask.new(:test) do |test|
|
|
40
40
|
test.libs << 'lib' << 'test'
|
41
41
|
test.pattern = 'test/**/test_*.rb'
|
42
42
|
test.verbose = false
|
43
|
+
test.warning = false
|
43
44
|
end
|
44
45
|
|
45
46
|
require 'rdoc/task'
|
data/bin/zold
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
2
|
# encoding: utf-8
|
3
3
|
#
|
4
|
-
# Copyright (c) 2018
|
4
|
+
# Copyright (c) 2018 Yegor Bugayenko
|
5
5
|
#
|
6
6
|
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
7
7
|
# of this software and associated documentation files (the 'Software'), to deal
|
@@ -32,13 +32,8 @@ require_relative '../lib/zold/wallets'
|
|
32
32
|
require_relative '../lib/zold/log'
|
33
33
|
require_relative '../lib/zold/key'
|
34
34
|
require_relative '../lib/zold/amount'
|
35
|
-
require_relative '../lib/zold/
|
36
|
-
require_relative '../lib/zold/
|
37
|
-
require_relative '../lib/zold/commands/balance'
|
38
|
-
require_relative '../lib/zold/commands/check'
|
39
|
-
require_relative '../lib/zold/commands/pull'
|
40
|
-
require_relative '../lib/zold/commands/push'
|
41
|
-
require_relative '../lib/zold/node/front'
|
35
|
+
require_relative '../lib/zold/copies'
|
36
|
+
require_relative '../lib/zold/remotes'
|
42
37
|
|
43
38
|
Encoding.default_external = Encoding::UTF_8
|
44
39
|
Encoding.default_internal = Encoding::UTF_8
|
@@ -46,21 +41,29 @@ Encoding.default_internal = Encoding::UTF_8
|
|
46
41
|
log = Zold::Log.new
|
47
42
|
|
48
43
|
begin
|
49
|
-
opts = Slop.parse(ARGV, strict:
|
44
|
+
opts = Slop.parse(ARGV, strict: false, suppress_errors: true) do |o|
|
50
45
|
o.banner = "Usage: zold [options] command [arguments]
|
51
46
|
Available commands:
|
47
|
+
#{Rainbow('remote').green}
|
48
|
+
Manage remote nodes
|
52
49
|
#{Rainbow('create').green}
|
53
50
|
Creates a new wallet with a random ID
|
54
|
-
#{Rainbow('
|
55
|
-
|
56
|
-
#{Rainbow('
|
57
|
-
|
58
|
-
#{Rainbow('
|
59
|
-
|
60
|
-
#{Rainbow('
|
61
|
-
|
62
|
-
#{Rainbow('
|
63
|
-
|
51
|
+
#{Rainbow('fetch').green} [ID...]
|
52
|
+
Fetch wallet copies from remote nodes
|
53
|
+
#{Rainbow('clean').green} [ID...]
|
54
|
+
Remove expired local copies
|
55
|
+
#{Rainbow('merge').green} [ID...]
|
56
|
+
Merge remote copies with the HEAD
|
57
|
+
#{Rainbow('pull').green} [ID...]
|
58
|
+
Fetch and then merge
|
59
|
+
#{Rainbow('show').green} [ID...]
|
60
|
+
Show all available information about the wallet
|
61
|
+
#{Rainbow('pay').green} source target amount details
|
62
|
+
Pay ZOLD from one wallet to another
|
63
|
+
#{Rainbow('status').green}
|
64
|
+
Show status of local copies
|
65
|
+
#{Rainbow('push').green} [ID...]
|
66
|
+
Push all/some local wallets or the ones required
|
64
67
|
#{Rainbow('node').green} port
|
65
68
|
Run node at the given TCP port
|
66
69
|
Available options:"
|
@@ -78,71 +81,149 @@ Available options:"
|
|
78
81
|
o.on '--no-colors', 'Disable colors in the ouput' do
|
79
82
|
Rainbow.enabled = false
|
80
83
|
end
|
84
|
+
o.on '--verbose', 'Enable extra logging information' do
|
85
|
+
log = Zold::Log::Verbose.new
|
86
|
+
end
|
81
87
|
o.on '-v', '--version', 'Show current version' do
|
82
|
-
|
88
|
+
log.info(Zold::VERSION)
|
83
89
|
exit
|
84
90
|
end
|
85
91
|
end
|
86
92
|
|
87
|
-
if opts.help?
|
93
|
+
raise 'Try --help' if opts.arguments.empty? && !opts.help?
|
94
|
+
|
95
|
+
if opts.help? && opts.arguments.empty?
|
88
96
|
log.info(opts.to_s)
|
89
97
|
exit
|
90
98
|
end
|
91
99
|
|
92
|
-
raise 'Command is required' if opts.arguments.empty?
|
93
|
-
|
94
100
|
command = opts.arguments[0]
|
95
101
|
|
102
|
+
args = ARGV[(ARGV.index(command) + 1)..-1]
|
103
|
+
|
96
104
|
wallets = Zold::Wallets.new(opts['dir'])
|
105
|
+
remotes = Zold::Remotes.new(File.join(opts['dir'], '.zold/remotes'))
|
106
|
+
copies = File.join(opts['dir'], '.zold/copies')
|
97
107
|
|
98
108
|
case command
|
99
109
|
when 'node'
|
100
|
-
|
101
|
-
Zold::
|
110
|
+
require_relative '../lib/zold/commands/node'
|
111
|
+
Zold::Node.new(log: log).run(args)
|
102
112
|
when 'create'
|
113
|
+
require_relative '../lib/zold/commands/create'
|
103
114
|
Zold::Create.new(
|
104
115
|
wallets: wallets,
|
105
116
|
pubkey: Zold::Key.new(file: opts['public-key']),
|
106
117
|
log: log
|
107
|
-
).run
|
108
|
-
when '
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
payer: wallets.find(Zold::Id.new(opts.arguments[1])),
|
113
|
-
receiver: wallets.find(Zold::Id.new(opts.arguments[2])),
|
114
|
-
amount: Zold::Amount.new(zld: opts.arguments[3].to_f),
|
115
|
-
pvtkey: Zold::Key.new(file: opts['private-key']),
|
116
|
-
log: log
|
117
|
-
).run
|
118
|
-
when 'balance'
|
119
|
-
raise "Wallet ID is required" if opts.arguments[1].nil?
|
120
|
-
Zold::Balance.new(
|
121
|
-
wallet: wallets.find(Zold::Id.new(opts.arguments[1])),
|
118
|
+
).run(args)
|
119
|
+
when 'remote'
|
120
|
+
require_relative '../lib/zold/commands/remote'
|
121
|
+
Zold::Remote.new(
|
122
|
+
remotes: remotes,
|
122
123
|
log: log
|
123
|
-
).run
|
124
|
-
when '
|
125
|
-
raise "
|
126
|
-
|
127
|
-
|
128
|
-
|
124
|
+
).run(args)
|
125
|
+
when 'pay'
|
126
|
+
raise "Payer wallet ID is required" if args[0].nil?
|
127
|
+
raise "Recepient wallet ID is required" if args[1].nil?
|
128
|
+
raise "Amount is required (in Zolds)" if args[2].nil?
|
129
|
+
require_relative '../lib/zold/commands/pay'
|
130
|
+
Zold::Pay.new(
|
131
|
+
payer: wallets.find(Zold::Id.new(args[0])),
|
132
|
+
receiver: Zold::Id.new(args[1]),
|
133
|
+
amount: Zold::Amount.new(zld: args[2].to_f),
|
134
|
+
details: opts.arguments[4] ? args[3] : '-',
|
135
|
+
pvtkey: Zold::Key.new(file: opts['private-key']),
|
129
136
|
log: log
|
130
|
-
).run
|
137
|
+
).run(args)
|
138
|
+
when 'show'
|
139
|
+
if args[0].nil?
|
140
|
+
require_relative '../lib/zold/commands/list'
|
141
|
+
Zold::List.new(
|
142
|
+
wallets: wallets,
|
143
|
+
log: log
|
144
|
+
).run(args)
|
145
|
+
else
|
146
|
+
require_relative '../lib/zold/commands/show'
|
147
|
+
args.each do |id|
|
148
|
+
Zold::Show.new(
|
149
|
+
wallet: wallets.find(Zold::Id.new(id)),
|
150
|
+
log: log
|
151
|
+
).run(args)
|
152
|
+
end
|
153
|
+
end
|
154
|
+
when 'fetch'
|
155
|
+
require_relative '../lib/zold/commands/fetch'
|
156
|
+
args.each do |id|
|
157
|
+
Zold::Fetch.new(
|
158
|
+
wallet: wallets.find(Zold::Id.new(id)),
|
159
|
+
remotes: remotes,
|
160
|
+
copies: Zold::Copies.new(File.join(copies, id)),
|
161
|
+
log: log
|
162
|
+
).run(args)
|
163
|
+
end
|
164
|
+
when 'clean'
|
165
|
+
require_relative '../lib/zold/commands/clean'
|
166
|
+
if args.empty?
|
167
|
+
args = Dir.new(copies).select { |f| f =~ /[0-9a-fA-F]{16}/ }
|
168
|
+
end
|
169
|
+
args.each do |id|
|
170
|
+
Zold::Clean.new(
|
171
|
+
copies: Zold::Copies.new(File.join(copies, id)),
|
172
|
+
log: log
|
173
|
+
).run(args)
|
174
|
+
end
|
175
|
+
when 'diff'
|
176
|
+
require_relative '../lib/zold/commands/diff'
|
177
|
+
args.each do |id|
|
178
|
+
Zold::Diff.new(
|
179
|
+
wallet: wallets.find(Zold::Id.new(id)),
|
180
|
+
copies: Zold::Copies.new(File.join(copies, id)),
|
181
|
+
log: log
|
182
|
+
).run(args)
|
183
|
+
end
|
184
|
+
when 'merge'
|
185
|
+
require_relative '../lib/zold/commands/merge'
|
186
|
+
require_relative '../lib/zold/commands/propagate'
|
187
|
+
args.each do |id|
|
188
|
+
Zold::Merge.new(
|
189
|
+
wallet: wallets.find(Zold::Id.new(id)),
|
190
|
+
copies: Zold::Copies.new(File.join(copies, id)),
|
191
|
+
log: log
|
192
|
+
).run(args)
|
193
|
+
Zold::Propagate.new(
|
194
|
+
wallet: wallets.find(Zold::Id.new(id)),
|
195
|
+
wallets: wallets,
|
196
|
+
log: log
|
197
|
+
).run(args)
|
198
|
+
end
|
131
199
|
when 'pull'
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
200
|
+
require_relative '../lib/zold/commands/fetch'
|
201
|
+
require_relative '../lib/zold/commands/merge'
|
202
|
+
args.each do |id|
|
203
|
+
Zold::Fetch.new(
|
204
|
+
wallet: wallets.find(Zold::Id.new(id)),
|
205
|
+
remotes: remotes,
|
206
|
+
copies: Zold::Copies.new(File.join(copies, id)),
|
207
|
+
log: log
|
208
|
+
).run(args)
|
209
|
+
Zold::Merge.new(
|
210
|
+
wallet: wallets.find(Zold::Id.new(id)),
|
211
|
+
copies: Zold::Copies.new(File.join(copies, id)),
|
212
|
+
log: log
|
213
|
+
).run(args)
|
214
|
+
end
|
136
215
|
when 'push'
|
216
|
+
require_relative '../lib/zold/commands/push'
|
137
217
|
Zold::Push.new(
|
138
|
-
wallet: wallets.find(Zold::Id.new(
|
218
|
+
wallet: wallets.find(Zold::Id.new(args[0])),
|
219
|
+
remotes: remotes,
|
139
220
|
log: log
|
140
|
-
).run
|
221
|
+
).run(args)
|
141
222
|
else
|
142
223
|
raise "Command '#{command}' is not supported"
|
143
224
|
end
|
144
225
|
rescue StandardError => ex
|
145
|
-
log.error(ex.message)
|
226
|
+
log.error("#{ex.message} (#{ex.class.name})")
|
146
227
|
puts ex.backtrace if opts['trace']
|
147
228
|
exit -1
|
148
229
|
end
|
data/features/cli.feature
CHANGED
@@ -12,6 +12,6 @@ Feature: Command Line Processing
|
|
12
12
|
Then Exit code is zero
|
13
13
|
|
14
14
|
Scenario: Wallet can be created
|
15
|
-
When I run bin/zold with "
|
15
|
+
When I run bin/zold with " --private-key id_rsa --public-key id_rsa.pub --trace create"
|
16
16
|
Then Exit code is zero
|
17
17
|
|
data/features/support/env.rb
CHANGED
@@ -0,0 +1,35 @@
|
|
1
|
+
#!/bin/bash
|
2
|
+
set -x
|
3
|
+
set -e
|
4
|
+
shopt -s expand_aliases
|
5
|
+
|
6
|
+
alias zold="$1"
|
7
|
+
|
8
|
+
port=`python -c 'import socket; s=socket.socket(); s.bind(("", 0)); print(s.getsockname()[1]); s.close()'`
|
9
|
+
|
10
|
+
mkdir server
|
11
|
+
cd server
|
12
|
+
zold node --host=localhost --port=${port} --bind-port=${port} --threads=0 &
|
13
|
+
pid=$!
|
14
|
+
trap "kill -9 $pid" EXIT
|
15
|
+
cd ..
|
16
|
+
|
17
|
+
while ! nc -z localhost ${port}; do
|
18
|
+
sleep 0.1
|
19
|
+
done
|
20
|
+
|
21
|
+
zold remote clean
|
22
|
+
zold remote add localhost ${port}
|
23
|
+
zold remote show
|
24
|
+
|
25
|
+
zold --public-key id_rsa.pub create 0000000000000000
|
26
|
+
zold --private-key id_rsa --trace pay 0000000000000000 af5788fcadd710c5 14.99 'To save the world!'
|
27
|
+
zold show
|
28
|
+
zold show 0000000000000000
|
29
|
+
|
30
|
+
zold push 0000000000000000
|
31
|
+
zold fetch 0000000000000000
|
32
|
+
zold diff 0000000000000000
|
33
|
+
zold --trace merge 0000000000000000
|
34
|
+
|
35
|
+
echo 'DONE'
|
data/lib/zold.rb
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
# Copyright (c) 2018
|
1
|
+
# Copyright (c) 2018 Yegor Bugayenko
|
2
2
|
#
|
3
3
|
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
4
4
|
# of this software and associated documentation files (the 'Software'), to deal
|
@@ -23,7 +23,7 @@ require_relative 'zold/version'
|
|
23
23
|
|
24
24
|
# Zold main module.
|
25
25
|
# Author:: Yegor Bugayenko (yegor256@gmail.com)
|
26
|
-
# Copyright:: Copyright (c) 2018
|
26
|
+
# Copyright:: Copyright (c) 2018 Yegor Bugayenko
|
27
27
|
# License:: MIT
|
28
28
|
module Zold
|
29
29
|
# to be implemented...
|
data/lib/zold/amount.rb
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
# Copyright (c) 2018
|
1
|
+
# Copyright (c) 2018 Yegor Bugayenko
|
2
2
|
#
|
3
3
|
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
4
4
|
# of this software and associated documentation files (the 'Software'), to deal
|
@@ -22,7 +22,7 @@ require 'rainbow'
|
|
22
22
|
|
23
23
|
# The amount.
|
24
24
|
# Author:: Yegor Bugayenko (yegor256@gmail.com)
|
25
|
-
# Copyright:: Copyright (c) 2018
|
25
|
+
# Copyright:: Copyright (c) 2018 Yegor Bugayenko
|
26
26
|
# License:: MIT
|
27
27
|
module Zold
|
28
28
|
# Amount
|
@@ -57,6 +57,14 @@ module Zold
|
|
57
57
|
@coins == other.to_i
|
58
58
|
end
|
59
59
|
|
60
|
+
def >(other)
|
61
|
+
@coins > other.to_i
|
62
|
+
end
|
63
|
+
|
64
|
+
def <(other)
|
65
|
+
@coins < other.to_i
|
66
|
+
end
|
67
|
+
|
60
68
|
def +(other)
|
61
69
|
Amount.new(coins: @coins + other.to_i)
|
62
70
|
end
|
@@ -1,4 +1,4 @@
|
|
1
|
-
# Copyright (c) 2018
|
1
|
+
# Copyright (c) 2018 Yegor Bugayenko
|
2
2
|
#
|
3
3
|
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
4
4
|
# of this software and associated documentation files (the 'Software'), to deal
|
@@ -18,29 +18,29 @@
|
|
18
18
|
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
19
19
|
# SOFTWARE.
|
20
20
|
|
21
|
+
require 'uri'
|
22
|
+
require 'json'
|
23
|
+
require 'time'
|
21
24
|
require_relative '../log.rb'
|
25
|
+
require_relative '../http.rb'
|
26
|
+
require_relative '../score.rb'
|
22
27
|
|
23
|
-
#
|
28
|
+
# CLEAN command.
|
24
29
|
# Author:: Yegor Bugayenko (yegor256@gmail.com)
|
25
|
-
# Copyright:: Copyright (c) 2018
|
30
|
+
# Copyright:: Copyright (c) 2018 Yegor Bugayenko
|
26
31
|
# License:: MIT
|
27
32
|
module Zold
|
28
|
-
#
|
29
|
-
class
|
30
|
-
def initialize(
|
31
|
-
@
|
32
|
-
@receiver = receiver
|
33
|
-
@amount = amount
|
34
|
-
@pvtkey = pvtkey
|
33
|
+
# CLEAN pulling command
|
34
|
+
class Clean
|
35
|
+
def initialize(copies:, log: Log::Quiet.new)
|
36
|
+
@copies = copies
|
35
37
|
@log = log
|
36
38
|
end
|
37
39
|
|
38
|
-
def run
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
@log.info("#{@amount} sent from #{@payer} to #{@receiver}")
|
43
|
-
txn[:id]
|
40
|
+
def run(_ = [])
|
41
|
+
@copies.clean
|
42
|
+
@log.debug("Expired local copies removed for #{@copies}, \
|
43
|
+
#{@copies.all.count} left")
|
44
44
|
end
|
45
45
|
end
|
46
46
|
end
|
data/lib/zold/commands/create.rb
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
# Copyright (c) 2018
|
1
|
+
# Copyright (c) 2018 Yegor Bugayenko
|
2
2
|
#
|
3
3
|
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
4
4
|
# of this software and associated documentation files (the 'Software'), to deal
|
@@ -24,7 +24,7 @@ require_relative '../id.rb'
|
|
24
24
|
|
25
25
|
# CREATE command.
|
26
26
|
# Author:: Yegor Bugayenko (yegor256@gmail.com)
|
27
|
-
# Copyright:: Copyright (c) 2018
|
27
|
+
# Copyright:: Copyright (c) 2018 Yegor Bugayenko
|
28
28
|
# License:: MIT
|
29
29
|
module Zold
|
30
30
|
# Create command
|
@@ -35,11 +35,13 @@ module Zold
|
|
35
35
|
@log = log
|
36
36
|
end
|
37
37
|
|
38
|
-
def run
|
39
|
-
id = Id.new
|
38
|
+
def run(args = [])
|
39
|
+
id = args.empty? ? Id.new : Id.new(args[0])
|
40
40
|
wallet = @wallets.find(id)
|
41
41
|
wallet.init(id, @pubkey)
|
42
|
-
@log.info(
|
42
|
+
@log.info(wallet.id)
|
43
|
+
@log.debug("Wallet #{Rainbow(wallet).green} \
|
44
|
+
created at #{@wallets.path}")
|
43
45
|
wallet
|
44
46
|
end
|
45
47
|
end
|