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.



60
61
62
63
64
65
66
67
# File 'lib/zold/wallet.rb', line 60

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



69
70
71
# File 'lib/zold/wallet.rb', line 69

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

#add(txn) ⇒ Object

Add a transaction to the wallet.



160
161
162
163
164
165
166
167
168
169
170
171
172
173
# File 'lib/zold/wallet.rb', line 160

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.



212
213
214
215
# File 'lib/zold/wallet.rb', line 212

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

#balanceObject

Returns current wallet balance.



133
134
135
# File 'lib/zold/wallet.rb', line 133

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

#digestObject

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



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

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

#exists?Boolean

Returns TRUE if the wallet file exists.

Returns:

  • (Boolean)


103
104
105
# File 'lib/zold/wallet.rb', line 103

def exists?
  File.exist?(path)
end

#flushObject

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



240
241
242
243
# File 'lib/zold/wallet.rb', line 240

def flush
  @head.flush
  @txns.flush
end

#idObject

Returns the wallet ID.



128
129
130
# File 'lib/zold/wallet.rb', line 128

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)


177
178
179
180
# File 'lib/zold/wallet.rb', line 177

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)


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

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.



113
114
115
116
117
118
119
120
# File 'lib/zold/wallet.rb', line 113

def init(id, pubkey, overwrite: false, network: 'test')
  raise "File '#{path}' already exists" if File.exist?(path) && !overwrite
  raise "Invalid network name '#{network}'" unless network =~ /^[a-z]{4,16}$/
  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.



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

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

#mnemoObject

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



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

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.



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

def mtime
  File.mtime(path)
end

#networkObject

Returns the network ID of the wallet.



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

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

#pathObject

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



108
109
110
# File 'lib/zold/wallet.rb', line 108

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)


192
193
194
# File 'lib/zold/wallet.rb', line 192

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

#protocolObject

Returns the protocol ID of the wallet file.



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

def protocol
  v = @head.fetch[1]
  raise "Invalid protocol version name '#{v}'" unless v =~ /^[0-9]+$/
  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.



233
234
235
236
# File 'lib/zold/wallet.rb', line 233

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)


123
124
125
# File 'lib/zold/wallet.rb', line 123

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.



219
220
221
222
# File 'lib/zold/wallet.rb', line 219

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.



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

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



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

def to_s
  id.to_s
end

#to_textObject

Convert the content of the wallet to the text.



84
85
86
# File 'lib/zold/wallet.rb', line 84

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

#txnsObject

Retrieve the total list of all transactions.



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

def txns
  @txns.fetch
end