Class: Vendi::Machine

Inherits:
Object
  • Object
show all
Includes:
Minter, Monitor, Utils
Defined in:
lib/vendi/machine.rb

Overview

Vending Machine: fill it with NFTs and serve to the hungry and in need!

Instance Attribute Summary collapse

Instance Method Summary collapse

Methods included from Minter

#asset_name, #construct_sign_submit, #keys_to_mint, #metadatas, #mint_nft, #mint_payload, #outgoing_tx_ok?, #prepare_metadata, #update_failed_mints, #update_metadata_files, #wait_for_tx_in_ledger

Methods included from Monitor

#get_dest_addr, #get_incoming_txs, #get_transactions_to_process, #incoming_tx_ok?

Methods included from Utils

#as_ada, #eventually, #from_json, #to_json

Constructor Details

#initialize(wallet_opts = {}, log_level = :info, log_file = nil) ⇒ Machine

Returns a new instance of Machine.



13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
# File 'lib/vendi/machine.rb', line 13

def initialize(wallet_opts = {}, log_level = :info, log_file = nil)
  @cw = CardanoWallet.new(wallet_opts)
  progname = 'vendi'
  datetime_format = '%Y-%m-%d %H:%M:%S'
  @logger = if log_file
              Logger.new(log_file,
                         'daily',
                         # shift_size = 10,
                         progname: progname,
                         level: log_level,
                         datetime_format: datetime_format)
            else
              Logger.new($stdout,
                         progname: progname,
                         level: log_level,
                         datetime_format: datetime_format)
            end
  @config_dir = File.join(Dir.home, '.vendi-nft-machine')
end

Instance Attribute Details

#config_dirObject

Returns the value of attribute config_dir.



11
12
13
# File 'lib/vendi/machine.rb', line 11

def config_dir
  @config_dir
end

#cwObject (readonly)

Returns the value of attribute cw.



10
11
12
# File 'lib/vendi/machine.rb', line 10

def cw
  @cw
end

#loggerObject (readonly)

Returns the value of attribute logger.



10
11
12
# File 'lib/vendi/machine.rb', line 10

def logger
  @logger
end

Instance Method Details

#collection_dir(collection_name) ⇒ Object



33
34
35
# File 'lib/vendi/machine.rb', line 33

def collection_dir(collection_name)
  File.join(@config_dir, collection_name)
end

#config(collection_name) ⇒ Object



57
58
59
# File 'lib/vendi/machine.rb', line 57

def config(collection_name)
  from_json(config_path(collection_name))
end

#config_path(collection_name) ⇒ Object



37
38
39
# File 'lib/vendi/machine.rb', line 37

def config_path(collection_name)
  File.join(collection_dir(collection_name), 'config.json')
end

#failed_mints(collection_name) ⇒ Object



89
90
91
# File 'lib/vendi/machine.rb', line 89

def failed_mints(collection_name)
  from_json(failed_mints_path(collection_name))
end

#failed_mints_path(collection_name) ⇒ Object



53
54
55
# File 'lib/vendi/machine.rb', line 53

def failed_mints_path(collection_name)
  File.join(collection_dir(collection_name), 'failed-mints.json')
end

#fill(collection_name, price, nft_count, skip_wallet: false) ⇒ Object

Fill vending machine with exemplary set of CIP-25 metadata for minting, set up basic config and create wallet for minting



99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
# File 'lib/vendi/machine.rb', line 99

def fill(collection_name, price, nft_count, skip_wallet: false)
  FileUtils.mkdir_p(collection_dir(collection_name))
  if skip_wallet
    @logger.info('Skipping wallet generation for your collection.')
    wallet_details = if File.exist?(config_path(collection_name))
                       c = config(collection_name)
                       c.delete(:price)
                       c
                     else
                       { wallet_id: '',
                         wallet_name: '',
                         wallet_pass: '',
                         wallet_address: '',
                         wallet_policy_id: '',
                         wallet_mnemonics: '' }
                     end
  else
    @logger.info('Generating wallet for your collection.')
    wallet_details = create_wallet("Vendi wallet - #{collection_name}")
  end

  @logger.info("Generating your NFT collection config into #{config_path(collection_name)}.")
  @logger.info("NFT price: #{as_ada(price.to_i)}.")
  config = { price: price.to_i }
  mnemonics = wallet_details[:wallet_mnemonics]
  wallet_details.delete(:wallet_mnemonics)
  config.merge!(wallet_details)
  set_config(collection_name, config)

  @logger.info("Generating exemplary CIP-25 metadata set into #{(collection_name)}.")
  metadatas = (collection_name, nft_count.to_i)
  (collection_name, metadatas)
  (collection_name, metadatas)
  (collection_name, {})
  set_failed_mints(collection_name, {})

  @logger.info('IMPORTANT NOTES! 👇')
  @logger.info('----------------')
  @logger.info("Check contents of #{collection_dir(collection_name)} and edit files as needed.")
  @logger.info('Before starting vending machine make sure your wallet is synced and has enough funds.')
  @logger.info("To fund your wallet send ADA to: #{wallet_details[:wallet_address]}")
  @logger.info("❗ Write down your wallet mnemonics: #{mnemonics}.")
end

#metadata(collection_name) ⇒ Object



81
82
83
# File 'lib/vendi/machine.rb', line 81

def (collection_name)
  from_json((collection_name))
end

#metadata_path(collection_name) ⇒ Object



41
42
43
# File 'lib/vendi/machine.rb', line 41

def (collection_name)
  File.join(collection_dir(collection_name), 'metadata.json')
end

#metadata_sent(collection_name) ⇒ Object



73
74
75
# File 'lib/vendi/machine.rb', line 73

def (collection_name)
  from_json((collection_name))
end

#metadata_sent_path(collection_name) ⇒ Object



49
50
51
# File 'lib/vendi/machine.rb', line 49

def (collection_name)
  File.join(collection_dir(collection_name), 'metadata-sent.json')
end

#metadata_vending(collection_name) ⇒ Object



65
66
67
# File 'lib/vendi/machine.rb', line 65

def (collection_name)
  from_json((collection_name))
end

#metadata_vending_path(collection_name) ⇒ Object



45
46
47
# File 'lib/vendi/machine.rb', line 45

def (collection_name)
  File.join(collection_dir(collection_name), 'metadata-vending.json')
end

#serve(collection_name, vend_max = 1) ⇒ Object

Turn on vending machine and make it serve NFTs for anyone who dares to pay the ‘price’ to the ‘address’, that is specified in the config_file



145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
# File 'lib/vendi/machine.rb', line 145

def serve(collection_name, vend_max = 1)
  (collection_name, {}) unless File.exist?((collection_name))

  c = config(collection_name)
  wid = c[:wallet_id]
  address = c[:wallet_address]
  policy_id = c[:wallet_policy_id]
  price = c[:price]
  wallet = @cw.shelley.wallets.get(wid)

  raise "Wallet #{wid} does not exist!" if wallet.code == 404
  raise "Wallet #{wid} is not synced (#{wallet['state']})!" if wallet['state']['status'] != 'ready'
  raise "Wallet #{wid} has no funds!" if (wallet['balance']['available']['quantity']).zero?

  @logger.info 'Vending machine started.'
  @logger.info "Wallet id: #{wid}"
  @logger.info "Policy id: #{policy_id}"
  @logger.info "Address: #{address}"
  @logger.info "NFT price: #{as_ada(price)}"
  @logger.info "Vend max NFTs: #{vend_max}"
  @logger.info "Original NFT stock: #{(collection_name).size}"
  @logger.info '----------------'
  unless File.exist?((collection_name))
    @logger.info "Making copy of #{(collection_name)} to #{(collection_name)}."
    FileUtils.cp((collection_name), (collection_name))
  end

  txs = get_incoming_txs(wid)
  until (collection_name).empty?
    nfts = (collection_name)
    nfts_sent = (collection_name)
    wallet_balance = @cw.shelley.wallets.get(wid)['balance']['available']['quantity']
    @logger.info "[In stock: #{nfts.size}, Sent: #{nfts_sent.size}, NFT price: #{as_ada(price)}, Vend max: #{vend_max}, Balance: #{as_ada(wallet_balance)}]"
    n = @cw.misc.network.information
    unless n['sync_progress']['status'] == 'ready'
      @logger.error "Network is not synced (#{n['sync_progress']['status']} #{n['sync_progress']['progress']['quantity']}%), waiting..."
      sleep 5
    end

    txs_new = get_incoming_txs(wid)
    if txs.size < txs_new.size
      txs_to_check = get_transactions_to_process(txs_new, txs)
      @logger.info "New txs arrived: #{txs_to_check.size}"
      @logger.info (txs_to_check.map { |t| t['id'] }).to_s

      txs_to_check.each do |t|
        @logger.info "Checking #{t['id']}"
        if incoming_tx_ok?(t, address, price)
          @logger.info 'OK! VENDING!'
          @logger.info '----------------'
          dest_addr = get_dest_addr(t, address)
          minted = mint_nft(collection_name, t['amount']['quantity'], vend_max, dest_addr)
          tx_res = minted[:tx_res]
          keys = minted[:keys]
          if outgoing_tx_ok?(tx_res)
            mint_tx_id = tx_res.last['id']
            wait_for_tx_in_ledger(wid, mint_tx_id)
            # update metadata files
            (keys, collection_name)
          else
            @logger.error 'Minting tx failed!'
            @logger.error "Construct tx: #{JSON.pretty_generate(tx_res[0])}"
            @logger.error "Sign tx: #{JSON.pretty_generate(tx_res[1])}"
            @logger.error "Submit tx: #{JSON.pretty_generate(tx_res[2])}"
            @logger.warn "Updating #{failed_mints_path(collection_name)} file."
            update_failed_mints(collection_name, t['id'], tx_res, keys)
          end
          @logger.info '----------------'

        else
          amt = t['amount']['quantity']
          @logger.warn "NOT VENDING! Amt: #{as_ada(amt)}, Tx: #{t['id']}"
          @logger.warn "Updating #{failed_mints_path(collection_name)} file."
          reason = if amt.to_i < price.to_i
                     "wrong_amount = #{as_ada(amt)}"
                   else
                     "wrong_address = #{address} was not in incoming tx outputs"
                   end
          update_failed_mints(collection_name, t['id'], reason, keys)
        end
      end

      txs = txs_new
    end

    sleep 5
  end
  @logger.info 'Turning off! Vending machine empty!'
end

#set_config(collection_name, configuration) ⇒ Object



61
62
63
# File 'lib/vendi/machine.rb', line 61

def set_config(collection_name, configuration)
  to_json(config_path(collection_name), configuration)
end

#set_failed_mints(collection_name, failed_mints) ⇒ Object



93
94
95
# File 'lib/vendi/machine.rb', line 93

def set_failed_mints(collection_name, failed_mints)
  to_json(failed_mints_path(collection_name), failed_mints)
end

#set_metadata(collection_name, metadata) ⇒ Object



85
86
87
# File 'lib/vendi/machine.rb', line 85

def (collection_name, )
  to_json((collection_name), )
end

#set_metadata_sent(collection_name, metadata) ⇒ Object



77
78
79
# File 'lib/vendi/machine.rb', line 77

def (collection_name, )
  to_json((collection_name), )
end

#set_metadata_vending(collection_name, metadata) ⇒ Object



69
70
71
# File 'lib/vendi/machine.rb', line 69

def (collection_name, )
  to_json((collection_name), )
end