Class: Zold::Wallet

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

Overview

A single wallet

Constant Summary collapse

MAINET =

The name of the main production network. All other networks must have different names.

'zold'
EXT =

The extension of the wallet files

'.z'

Instance Method Summary collapse

Constructor Details

#initialize(file) ⇒ Wallet

The constructor of the wallet, from the file. The file may be absent at the time of creating the object. Later, don’t forget to call init() in order to initialize the wallet, if it’s absent.



43
44
45
46
47
48
49
50
# File 'lib/zold/wallet.rb', line 43

def initialize(file)
  unless file.end_with?(Wallet::EXT, Copies::EXT)
    raise "Wallet file must end with #{Wallet::EXT} or #{Copies::EXT}: #{file}"
  end
  @file = File.absolute_path(file)
  @txns = CachedTxns.new(Txns.new(@file))
  @head = CachedHead.new(Head.new(@file))
end

Instance Method Details

#==(other) ⇒ Object



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

def ==(other)
  to_s == other.to_s
end

#add(txn) ⇒ Object

Add a transaction to the wallet.



143
144
145
146
147
148
149
150
151
152
153
154
155
156
# File 'lib/zold/wallet.rb', line 143

def add(txn)
  raise 'The txn has to be of type Txn' unless txn.is_a?(Txn)
  raise "Wallet #{id} can't pay itself: #{txn}" if txn.bnf == id
  raise "The amount can't be zero in #{id}: #{txn}" if txn.amount.zero?
  if txn.amount.negative? && includes_negative?(txn.id)
    raise "Negative transaction with the same ID #{txn.id} already exists in #{id}"
  end
  if txn.amount.positive? && includes_positive?(txn.id, txn.bnf)
    raise "Positive transaction with the same ID #{txn.id} and BNF #{txn.bnf} already exists in #{id}"
  end
  raise "The tax payment already exists in #{id}: #{txn}" if Tax.new(self).exists?(txn.details)
  File.open(path, 'a') { |f| f.print "#{txn}\n" }
  @txns.flush
end

#ageObject

Age of wallet in hours.



195
196
197
198
# File 'lib/zold/wallet.rb', line 195

def age
  list = txns
  list.empty? ? 0 : (Time.now - list.min_by(&:date).date) / (60 * 60)
end

#balanceObject

Returns current wallet balance.



116
117
118
# File 'lib/zold/wallet.rb', line 116

def balance
  txns.inject(Amount::ZERO) { |sum, t| sum + t.amount }
end

#digestObject

Returns a pseudo-unique hexadecimal digest of the wallet content.



190
191
192
# File 'lib/zold/wallet.rb', line 190

def digest
  OpenSSL::Digest::SHA256.file(path).hexdigest
end

#exists?Boolean

Returns TRUE if the wallet file exists.

Returns:

  • (Boolean)


86
87
88
# File 'lib/zold/wallet.rb', line 86

def exists?
  File.exist?(path)
end

#flushObject

Flush the in-memory cache and force the object to load all data from the disc again.



223
224
225
226
# File 'lib/zold/wallet.rb', line 223

def flush
  @head.flush
  @txns.flush
end

#idObject

Returns the wallet ID.



111
112
113
# File 'lib/zold/wallet.rb', line 111

def id
  Id.new(@head.fetch[2])
end

#includes_negative?(id, bnf = nil) ⇒ Boolean

Returns TRUE if the wallet contains a payment sent with the specified ID, which was sent to the specified beneficiary.

Returns:

  • (Boolean)


160
161
162
163
# File 'lib/zold/wallet.rb', line 160

def includes_negative?(id, bnf = nil)
  raise 'The txn ID has to be of type Integer' unless id.is_a?(Integer)
  !txns.find { |t| t.id == id && (bnf.nil? || t.bnf == bnf) && t.amount.negative? }.nil?
end

#includes_positive?(id, bnf) ⇒ Boolean

Returns TRUE if the wallet contains a payment received with the specified ID, which was sent by the specified beneficiary.

Returns:

  • (Boolean)


167
168
169
170
171
# File 'lib/zold/wallet.rb', line 167

def includes_positive?(id, bnf)
  raise 'The txn ID has to be of type Integer' unless id.is_a?(Integer)
  raise 'The bnf has to be of type Id' unless bnf.is_a?(Id)
  !txns.find { |t| t.id == id && t.bnf == bnf && !t.amount.negative? }.nil?
end

#init(id, pubkey, overwrite: false, network: 'test') ⇒ Object

Creates an empty wallet with the specified ID and public key.



96
97
98
99
100
101
102
103
# File 'lib/zold/wallet.rb', line 96

def init(id, pubkey, overwrite: false, network: 'test')
  raise "File '#{path}' already exists" if File.exist?(path) && !overwrite
  raise "Invalid network name '#{network}'" unless /^[a-z]{4,16}$/.match?(network)
  FileUtils.mkdir_p(File.dirname(path))
  File.write(path, "#{network}\n#{PROTOCOL}\n#{id}\n#{pubkey.to_pub}\n\n")
  @txns.flush
  @head.flush
end

#keyObject

Returns the public key of the wallet.



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

def key
  Key.new(text: @head.fetch[3])
end

#mnemoObject

Returns a convenient printable mnemo code of the wallet (mostly useful for logs).



62
63
64
# File 'lib/zold/wallet.rb', line 62

def mnemo
  "#{id}/#{balance.to_zld(4)}/#{txns.count}t/#{digest[0, 6]}/#{Size.new(size)}"
end

#mtimeObject

Returns the time of when the wallet file was recently modified.



185
186
187
# File 'lib/zold/wallet.rb', line 185

def mtime
  File.mtime(path)
end

#networkObject

Returns the network ID of the wallet.



72
73
74
75
76
# File 'lib/zold/wallet.rb', line 72

def network
  n = @head.fetch[0]
  raise "Invalid network name '#{n}'" unless /^[a-z]{4,16}$/.match?(n)
  n
end

#pathObject

Returns the absolute path of the wallet file (it may be absent).



91
92
93
# File 'lib/zold/wallet.rb', line 91

def path
  @file
end

#prefix?(prefix) ⇒ Boolean

Returns TRUE if the public key of the wallet includes this payment prefix of the invoice.

Returns:

  • (Boolean)


175
176
177
# File 'lib/zold/wallet.rb', line 175

def prefix?(prefix)
  key.to_pub.include?(prefix)
end

#protocolObject

Returns the protocol ID of the wallet file.



79
80
81
82
83
# File 'lib/zold/wallet.rb', line 79

def protocol
  v = @head.fetch[1]
  raise "Invalid protocol version name '#{v}'" unless /^[0-9]+$/.match?(v)
  v.to_i
end

#refurbishObject

Resaves the content of the wallet to the disc in the right format. All unnecessary space and EOL-s are removed. This operation is required in order to make sure two wallets with the same content are identical, no matter whether they were formatted differently.



216
217
218
219
# File 'lib/zold/wallet.rb', line 216

def refurbish
  File.write(path, "#{(@head.fetch + [''] + @txns.fetch.map(&:to_s)).join("\n")}\n")
  @txns.flush
end

#root?Boolean

Returns TRUE if it’s a root wallet.

Returns:

  • (Boolean)


106
107
108
# File 'lib/zold/wallet.rb', line 106

def root?
  id == Id::ROOT
end

#sizeObject

Size of the wallet file in bytes. If the file doesn’t exist an exception will be raised.



202
203
204
205
# File 'lib/zold/wallet.rb', line 202

def size
  raise "The wallet file #{path} doesn't exist" unless File.exist?(path)
  File.size(path)
end

#sub(amount, invoice, pvt, details = '-', time: Time.now) ⇒ Object

Add a payment transaction to the wallet.



121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
# File 'lib/zold/wallet.rb', line 121

def sub(amount, invoice, pvt, details = '-', time: Time.now)
  raise 'The amount has to be of type Amount' unless amount.is_a?(Amount)
  raise "The amount can't be negative: #{amount}" if amount.negative?
  raise 'The pvt has to be of type Key' unless pvt.is_a?(Key)
  prefix, target = invoice.split('@')
  tid = max + 1
  raise 'Too many transactions already, can\'t add more' if max > 0xffff
  txn = Txn.new(
    tid,
    time,
    amount * -1,
    prefix,
    Id.new(target),
    details
  )
  txn = txn.signed(pvt, id)
  raise "Invalid private key for the wallet #{id}" unless Signature.new(network).valid?(key, id, txn)
  add(txn)
  txn
end

#to_sObject



56
57
58
# File 'lib/zold/wallet.rb', line 56

def to_s
  id.to_s
end

#to_textObject

Convert the content of the wallet to the text.



67
68
69
# File 'lib/zold/wallet.rb', line 67

def to_text
  (@head.fetch + [''] + @txns.fetch.map(&:to_text)).join("\n")
end

#txnsObject

Retrieve the total list of all transactions.



208
209
210
# File 'lib/zold/wallet.rb', line 208

def txns
  @txns.fetch
end