vendi 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 19f37dec74417ace8e5980584edc2e5c9b02fb03e5480abf35f2d929126df521
4
+ data.tar.gz: bc8ac9b3addadc2c10230468ed18a2081a86c42681ec2beccdd798ba6b1b311f
5
+ SHA512:
6
+ metadata.gz: f7b93150c1274e0f5bf81687ba4e42ba5bd291f747397c900ad2d73aa986ac1a926cad2c5bf579aea7b78012b0b02a99ad838df4cbfdc1f152675e84566aceb2
7
+ data.tar.gz: 79644c8d01cd86d11803ec04c9cc2f692537009a20bdeb2753ef679a9301317df078b913a532383d8612fc65eba85cd3ba9a597627d92d35deac88c13fd66cd4
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2022 Piotr Stachyra
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,38 @@
1
+ # Vendi
2
+
3
+ Vendi is simple CNFT vending machine based on [`cardano-wallet`](https://github.com/input-output-hk/cardano-wallet).
4
+ You need to have `cardano-wallet` started and synced.
5
+
6
+ It... seems to work, check out the [Demo](https://github.com/piotr-iohk/vendi/tree/master/demo). :)
7
+
8
+ ## Installation
9
+
10
+ **Rubygem:**
11
+
12
+ $ gem install vendi
13
+
14
+ ## Usage
15
+
16
+ Fill vending machine with exemplary NFT collection:
17
+
18
+ $ vendi fill --collection TestBudz --price 10000000 --nft-count 100
19
+
20
+ Now check out `$HOME/.vendi-nft-machine/TestBudz`, refine configs as you prefer.
21
+ When ready start vending machine:
22
+
23
+ $ vendi serve --collection TestBudz
24
+
25
+
26
+
27
+ ## Contributing
28
+
29
+ Bug reports and pull requests are welcome on GitHub at https://github.com/piotr-iohk/vendi. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [code of conduct](https://github.com/piotr-iohk/vendi/blob/master/CODE_OF_CONDUCT.md).
30
+
31
+
32
+ ## License
33
+
34
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
35
+
36
+ ## Code of Conduct
37
+
38
+ Everyone interacting in the Vendi project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/piotr-iohk/vendi/blob/master/CODE_OF_CONDUCT.md).
data/bin/console ADDED
@@ -0,0 +1,15 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require 'bundler/setup'
5
+ require 'vendi'
6
+
7
+ # You can add fixtures and/or initialization code here to make experimenting
8
+ # with your gem easier. You can also use a different console, if you like.
9
+
10
+ # (If you use this, don't forget to add pry to your Gemfile!)
11
+ # require "pry"
12
+ # Pry.start
13
+
14
+ require 'irb'
15
+ IRB.start(__FILE__)
data/bin/rebuild ADDED
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env bash
2
+ gem build *.gemspec -o vendi.gem
3
+ gem install vendi.gem
data/bin/setup ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
data/bin/vendi ADDED
@@ -0,0 +1,91 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require 'vendi'
5
+ require 'docopt'
6
+
7
+ doc = <<~DOCOPT
8
+ Vendi - CNFT Vending Machine.
9
+
10
+ Usage:
11
+ #{File.basename(__FILE__)} fill --collection <name> --price <lovelace> --nft-count <int> [--wallet-port <port>]
12
+ #{File.basename(__FILE__)} serve --collection <name> [--wallet-port <port>] [--logfile <file>]
13
+ #{File.basename(__FILE__)} -v | --version
14
+ #{File.basename(__FILE__)} -h | --help
15
+
16
+ Options:
17
+ fill Setup vending machine by filling it with exemplary set of NFT CIP-25 metadata
18
+ and creating a source wallet for minting.
19
+ serve Start vending machine.
20
+ --collection <name> Name of the collection.
21
+ --price <lovelace> Single NFT price in lovelace.
22
+ --nft-count <int> How many NFTs would you like to generate.
23
+ --wallet-port <port> Cardano-wallet port [default: 8090].
24
+ --logfile <file> Logfile (will be rotated daily).
25
+
26
+ -v --version Check #{File.basename(__FILE__)} version.
27
+ -h --help This help.
28
+
29
+ Example:
30
+ Fill vending machine with exemplary NFT collection:
31
+
32
+ $ vendi fill --collection TestBudz --price 10000000 --nft-count 100
33
+
34
+ Now check out $HOME/.vendi-nft-machine/TestBudz, refine configs as you prefer.
35
+ When ready start vending machine:
36
+
37
+ $ vendi serve --collection TestBudz
38
+
39
+ DOCOPT
40
+
41
+ begin
42
+ o = Docopt.docopt(doc)
43
+
44
+ warn Vendi::VERSION if o['--version']
45
+
46
+ if o['fill']
47
+ collection_name = o['--collection']
48
+ price = o['--price']
49
+ nft_count = o['--nft-count']
50
+ wallet_port = o['--wallet-port']
51
+ vendi = Vendi.init({ port: wallet_port.to_i })
52
+ begin
53
+ if File.directory?(File.join(vendi.config_dir, collection_name))
54
+ $stdout.print "Collection '#{collection_name}' already exists do you want to overwrite? (y/n): "
55
+ yn = $stdin.gets.chomp
56
+ raise Interrupt unless %w[y Y].include?(yn)
57
+ end
58
+ vendi.fill(collection_name, price, nft_count)
59
+ rescue StandardError => e
60
+ vendi.logger.error e.message
61
+ rescue Interrupt
62
+ warn ''
63
+ vendi.logger.info 'Vending machine filling stopped.'
64
+ end
65
+ end
66
+
67
+ if o['serve']
68
+ collection_name = o['--collection']
69
+ wallet_port = o['--wallet-port']
70
+ logfile = o['--logfile']
71
+ vendi = if logfile
72
+ Vendi.init({ port: wallet_port.to_i }, :info, logfile)
73
+ else
74
+ Vendi.init({ port: wallet_port.to_i })
75
+ end
76
+ begin
77
+ vendi.serve(collection_name)
78
+ rescue Errno::ECONNREFUSED => e
79
+ # retry if cannot connect to cardano-wallet
80
+ vendi.logger.error e.message
81
+ sleep 5
82
+ retry
83
+ rescue StandardError => e
84
+ vendi.logger.error e.message
85
+ rescue Interrupt
86
+ vendi.logger.info 'Vending machine stopped.'
87
+ end
88
+ end
89
+ rescue Docopt::Exit => e
90
+ puts e.message
91
+ end
@@ -0,0 +1,245 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Vendi
4
+ # Vending Machine: fill it with NFTs and serve to the hungry and in need!
5
+ class Machine
6
+ include Vendi::Utils
7
+ include Vendi::Monitor
8
+ include Vendi::Minter
9
+
10
+ attr_reader :logger, :cw
11
+ attr_accessor :config_dir
12
+
13
+ def initialize(wallet_opts = {}, log_level = :info, log_file = nil)
14
+ @cw = CardanoWallet.new(wallet_opts)
15
+ progname = 'vendi'
16
+ datetime_format = '%Y-%m-%d %H:%M:%S'
17
+ @logger = if log_file
18
+ Logger.new(log_file,
19
+ 'daily',
20
+ # shift_size = 10,
21
+ progname: progname,
22
+ level: log_level,
23
+ datetime_format: datetime_format)
24
+ else
25
+ Logger.new($stdout,
26
+ progname: progname,
27
+ level: log_level,
28
+ datetime_format: datetime_format)
29
+ end
30
+ @config_dir = File.join(Dir.home, '.vendi-nft-machine')
31
+ end
32
+
33
+ def collection_dir(collection_name)
34
+ File.join(@config_dir, collection_name)
35
+ end
36
+
37
+ def config_path(collection_name)
38
+ File.join(collection_dir(collection_name), 'config.json')
39
+ end
40
+
41
+ def metadata_path(collection_name)
42
+ File.join(collection_dir(collection_name), 'metadata.json')
43
+ end
44
+
45
+ def metadata_vending_path(collection_name)
46
+ File.join(collection_dir(collection_name), 'metadata-vending.json')
47
+ end
48
+
49
+ def metadata_sent_path(collection_name)
50
+ File.join(collection_dir(collection_name), 'metadata-sent.json')
51
+ end
52
+
53
+ def config(collection_name)
54
+ from_json(config_path(collection_name))
55
+ end
56
+
57
+ def metadata_vending(collection_name)
58
+ from_json(metadata_vending_path(collection_name))
59
+ end
60
+
61
+ def metadata_sent(collection_name)
62
+ from_json(metadata_sent_path(collection_name))
63
+ end
64
+
65
+ def metadata(collection_name)
66
+ from_json(metadata_path(collection_name))
67
+ end
68
+
69
+ def set_metadata(collection_name, metadata)
70
+ to_json(metadata_path(collection_name), metadata)
71
+ end
72
+
73
+ def set_metadata_sent(collection_name, metadata)
74
+ to_json(metadata_sent_path(collection_name), metadata)
75
+ end
76
+
77
+ def set_metadata_vending(collection_name, metadata)
78
+ to_json(metadata_vending_path(collection_name), metadata)
79
+ end
80
+
81
+ def set_config(collection_name, configuration)
82
+ to_json(config_path(collection_name), configuration)
83
+ end
84
+
85
+ # Fill vending machine with exemplary set of CIP-25 metadata for minting,
86
+ # set up basic config and create wallet for minting
87
+ def fill(collection_name, price, nft_count, skip_wallet: false)
88
+ FileUtils.mkdir_p(collection_dir(collection_name))
89
+ if skip_wallet
90
+ @logger.info('Skipping wallet generation for your collection.')
91
+ wallet_details = { wallet_id: '',
92
+ wallet_name: '',
93
+ wallet_pass: '',
94
+ wallet_address: '',
95
+ wallet_policy_id: '',
96
+ wallet_mnemonics: '' }
97
+ else
98
+ @logger.info('Generating wallet for your collection.')
99
+ wallet_details = create_wallet("Vendi wallet - #{collection_name}")
100
+ end
101
+
102
+ @logger.info("Generating your NFT collection config into #{config_path(collection_name)}.")
103
+ @logger.info("NFT price: #{as_ada(price.to_i)}.")
104
+ config = { price: price.to_i }
105
+ mnemonics = wallet_details[:wallet_mnemonics]
106
+ wallet_details.delete(:wallet_mnemonics)
107
+ config.merge!(wallet_details)
108
+ set_config(collection_name, config)
109
+
110
+ @logger.info("Generating exemplary CIP-25 metadata set into #{metadata_path(collection_name)}.")
111
+ metadatas = generate_metadata(collection_name, nft_count.to_i)
112
+ set_metadata(collection_name, metadatas)
113
+
114
+ @logger.info('IMPORTANT NOTES! 👇')
115
+ @logger.info('----------------')
116
+ @logger.info("Check contents of #{collection_dir(collection_name)} and edit files as needed.")
117
+ @logger.info('Before starting vending machine make sure your wallet is synced and has enough funds.')
118
+ @logger.info("To fund your wallet send ADA to: #{wallet_details[:wallet_address]}")
119
+ @logger.info("❗ Write down your wallet mnemonics: #{mnemonics}.")
120
+ end
121
+
122
+ # Turn on vending machine and make it serve NFTs for anyone who dares to
123
+ # pay the 'price' to the 'address', that is specified in the config_file
124
+ def serve(collection_name)
125
+ set_metadata_sent(collection_name, {}) unless File.exist?(metadata_sent_path(collection_name))
126
+
127
+ c = config(collection_name)
128
+ wid = c[:wallet_id]
129
+ pass = c[:wallet_pass]
130
+ address = c[:wallet_address]
131
+ policy_id = c[:wallet_policy_id]
132
+ price = c[:price]
133
+ wallet = @cw.shelley.wallets.get(wid)
134
+
135
+ raise "Wallet #{wid} does not exist!" if wallet.code == 404
136
+ raise "Wallet #{wid} is not synced (#{wallet['state']})!" if wallet['state']['status'] != 'ready'
137
+ raise "Wallet #{wid} has no funds!" if (wallet['balance']['available']['quantity']).zero?
138
+
139
+ @logger.info 'Vending machine started.'
140
+ @logger.info "Wallet id: #{wid}"
141
+ @logger.info "Address: #{address}"
142
+ @logger.info "NFT price: #{as_ada(price)}"
143
+ @logger.info "Original NFT stock: #{metadata(collection_name).size}"
144
+ @logger.info '----------------'
145
+ unless File.exist?(metadata_vending_path(collection_name))
146
+ @logger.info "Making copy of #{metadata_path(collection_name)} to #{metadata_vending_path(collection_name)}."
147
+ FileUtils.cp(metadata_path(collection_name), metadata_vending_path(collection_name))
148
+ end
149
+
150
+ txs = get_incoming_txs(wid)
151
+ until metadata_vending(collection_name).empty?
152
+ nfts = metadata_vending(collection_name)
153
+ nfts_sent = metadata_sent(collection_name)
154
+ wallet_balance = @cw.shelley.wallets.get(wid)['balance']['available']['quantity']
155
+ @logger.info "Vending machine [In stock: #{nfts.size}, Sent: #{nfts_sent.size}, NFT price: #{as_ada(price)}, Balance: #{as_ada(wallet_balance)}]"
156
+
157
+ txs_new = get_incoming_txs(wid)
158
+ if txs.size < txs_new.size
159
+ txs_to_check = get_transactions_to_process(tx_delta, txs)
160
+ @logger.info "New txs arrived: #{txs_to_check.size}"
161
+ @logger.info (txs_to_check.map { |t| t['id'] }).to_s
162
+
163
+ txs_to_check.each do |t|
164
+ @logger.info "Checking #{t['id']}"
165
+ if incoming_tx_ok?(t, address, price)
166
+ @logger.info 'OK! VENDING!'
167
+ @logger.info '----------------'
168
+ dest_addr = get_dest_addr(t, address)
169
+
170
+ # prepare metadata and mint payload
171
+ key = nfts.keys.sample
172
+ metadata = prepare_metadata(nfts, key, policy_id)
173
+ mint = mint_payload(asset_name(key.to_s), dest_addr)
174
+ @logger.debug JSON.pretty_generate(metadata)
175
+ @logger.debug JSON.pretty_generate(mint)
176
+
177
+ # mint
178
+ @logger.info "Minting NFT: #{key} to #{dest_addr}"
179
+ tx_res = construct_sign_submit(wid, pass, metadata, mint)
180
+ if outgoing_tx_ok?(tx_res)
181
+ mint_tx_id = tx_res.last['id']
182
+ wait_for_tx_in_ledger(wid, mint_tx_id)
183
+ # update metadata files
184
+ update_metadata_files(nfts, key, metadata_vending_path(collection_name), metadata_sent_path(collection_name))
185
+ else
186
+ @logger.error 'Minting tx failed!'
187
+ @logger.error "Construct tx: #{JSON.pretty_generate(tx_res[0])}"
188
+ @logger.error "Sign tx: #{JSON.pretty_generate(tx_res[1])}"
189
+ @logger.error "Submit tx: #{JSON.pretty_generate(tx_res[2])}"
190
+ end
191
+ @logger.info '----------------'
192
+
193
+ else
194
+ @logger.warn "NO GOOD! NOT VENDING! Tx: #{t['id']}"
195
+ end
196
+ end
197
+
198
+ txs = txs_new
199
+ end
200
+
201
+ sleep 5
202
+ end
203
+ @logger.info 'Turning off! Vending machine empty!'
204
+ end
205
+
206
+ private
207
+
208
+ def create_wallet(wallet_name = nil, wallet_pass = nil, wallet_mnemonics = nil)
209
+ wallet_name ||= 'Vendi wallet'
210
+ wallet_mnemonics ||= @cw.utils.mnemonic_sentence
211
+ wallet_pass ||= 'Secure Passphrase'
212
+ wallet = @cw.shelley.wallets.create({ name: wallet_name,
213
+ mnemonic_sentence: wallet_mnemonics,
214
+ passphrase: wallet_pass })
215
+
216
+ @logger.debug('!!!!! Write down wallet mnemonics !!!!')
217
+ @logger.debug(wallet_mnemonics.to_s)
218
+ wid = wallet['id']
219
+ wallet_address = @cw.shelley.addresses.list(wid).first['id']
220
+ wallet_policy_id = @cw.shelley.keys.create_policy_id(wid, Vendi::POLICY_SCRIPT_TEMPLATE)['policy_id']
221
+ { wallet_id: wallet['id'],
222
+ wallet_name: wallet_name,
223
+ wallet_pass: wallet_pass,
224
+ wallet_address: wallet_address,
225
+ wallet_policy_id: wallet_policy_id,
226
+ wallet_mnemonics: wallet_mnemonics }
227
+ end
228
+
229
+ def generate_metadata(nft_name_prefix, nft_count)
230
+ metadata = {}
231
+ 1.upto nft_count do |i|
232
+ nft_metadata = {
233
+ "#{nft_name_prefix}_#{i}" => {
234
+ 'name' => "#{nft_name_prefix.upcase} No #{i}",
235
+ 'image' => 'ipfs://QmRhTTbUrPYEw3mJGGhQqQST9k86v1DPBiTTWJGKDJsVFw',
236
+ 'Copyright' => "Vendi #{Time.now.year}",
237
+ 'Collection' => "#{nft_name_prefix} #{Time.now.year}"
238
+ }
239
+ }
240
+ metadata.merge!(nft_metadata)
241
+ end
242
+ metadata
243
+ end
244
+ end
245
+ end
@@ -0,0 +1,71 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Vendi
4
+ # helper methods for minting NFT
5
+ module Minter
6
+ def prepare_metadata(nfts, key, policy_id)
7
+ {
8
+ '721' => {
9
+ policy_id => {
10
+ key => nfts[key]
11
+ }
12
+ }
13
+ }
14
+ end
15
+
16
+ def update_metadata_files(nfts, key, metadata_vending_file, metadata_sent_file)
17
+ # metadata sent
18
+ m = { key => nfts[key] }
19
+ if File.exist? metadata_sent_file
20
+ sent = from_json(metadata_sent_file)
21
+ sent.merge!(m)
22
+ to_json(metadata_sent_file, sent)
23
+ else
24
+ to_json(metadata_sent_file, m)
25
+ end
26
+ # metadata available
27
+ nfts.delete(key)
28
+ to_json(metadata_vending_file, nfts)
29
+ end
30
+
31
+ # Build mint payload for construct tx
32
+ def mint_payload(asset_name, address, quantity = 1)
33
+ mint = { 'operation' => { 'mint' => { 'quantity' => quantity,
34
+ 'receiving_address' => address } },
35
+ 'policy_script_template' => Vendi::POLICY_SCRIPT_TEMPLATE }
36
+ mint['asset_name'] = asset_name unless asset_name.nil?
37
+ [mint]
38
+ end
39
+
40
+ # Construct -> Sign -> Submit
41
+ def construct_sign_submit(wid, pass, metadata, mint_payload)
42
+ tx_constructed = @cw.shelley.transactions.construct(wid,
43
+ nil,
44
+ nil,
45
+ metadata,
46
+ nil,
47
+ mint_payload,
48
+ nil)
49
+ # puts tx_constructed.request.options[:body]
50
+ tx_signed = @cw.shelley.transactions.sign(wid, pass, tx_constructed['transaction'])
51
+ # puts tx_signed
52
+ tx_submitted = @cw.shelley.transactions.submit(wid, tx_signed['transaction'])
53
+ # puts tx_submitted
54
+
55
+ [tx_constructed, tx_signed, tx_submitted]
56
+ end
57
+
58
+ def outgoing_tx_ok?(tx_res)
59
+ tx_constructed, tx_signed, tx_submitted = tx_res
60
+ tx_constructed.code == 202 && tx_signed.code == 202 && tx_submitted.code == 202
61
+ end
62
+
63
+ def wait_for_tx_in_ledger(wid, tx_id)
64
+ eventually "Tx #{tx_id} is in ledger" do
65
+ @logger.info "Waiting for #{tx_id} to get in_ledger"
66
+ tx = @cw.shelley.transactions.get(wid, tx_id)
67
+ tx.code == 200 && tx['status'] == 'in_ledger'
68
+ end
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Vendi
4
+ # helper methods for monitoring incoming transactions
5
+ module Monitor
6
+ def get_incoming_txs(wid)
7
+ @cw.shelley.transactions.list(wid).select { |t| t['direction'] == 'incoming' }
8
+ end
9
+
10
+ def get_transactions_to_process(txs_new, txs)
11
+ txs_new[0..(txs_new.size - txs.size - 1)]
12
+ end
13
+
14
+ # incoming tx is correct when the address is on any of the outputs (means that someone was sending to it)
15
+ # and tx amount is >= price set in the config
16
+ def incoming_tx_ok?(tx, address, price)
17
+ (tx['outputs'].any? { |o| (o['address'] == address) }) && (tx['amount']['quantity'] >= price)
18
+ end
19
+
20
+ # trying to naively get address to send back NFT, take first address from the output that isn't our address
21
+ # assuming that it is a change address
22
+ def get_dest_addr(tx, address)
23
+ output_dest_addr = tx['outputs'].map { |o| o['address'] }.reject { |a| a == address }.first
24
+
25
+ # if no address in outputs try to get one from inputs
26
+ output_dest_addr || tx['inputs'].map { |o| o['address'] }.reject { |a| a == address }.first
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,54 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Vendi
4
+ # general utility methods
5
+ module Utils
6
+ def from_json(file)
7
+ JSON.parse(File.read(file), { symbolize_names: true })
8
+ end
9
+
10
+ def to_json(file, hash)
11
+ File.write(file, JSON.pretty_generate(hash))
12
+ end
13
+
14
+ def as_ada(quantity)
15
+ res = quantity.to_f / 1_000_000
16
+ str_res = format('%.7f', res)
17
+ last = str_res[-1]
18
+ final = ''
19
+ until last == '.'
20
+ str_res.chop!
21
+ if last == '0'
22
+ final = str_res
23
+ break unless final[-1] == '0'
24
+ end
25
+ last = str_res[-1]
26
+ end
27
+ final.chop! if final[-1] == '.'
28
+ "#{final} ₳"
29
+ end
30
+
31
+ ##
32
+ # wait until action passed as &block returns true or TIMEOUT is reached
33
+ def eventually(label, &block)
34
+ current_time = Time.now
35
+ timeout_treshold = current_time + Vendi::TIMEOUT
36
+ while (block.call == false) && (current_time <= timeout_treshold)
37
+ sleep 5
38
+ current_time = Time.now
39
+ end
40
+ if current_time > timeout_treshold
41
+ @logger.error "Action '#{label}' did not resolve within timeout: #{Vendi::TIMEOUT}s"
42
+ false
43
+ else
44
+ true
45
+ end
46
+ end
47
+
48
+ ##
49
+ # encode string asset_name to hex representation
50
+ def asset_name(asset_name)
51
+ asset_name.unpack1('H*')
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Vendi
4
+ VERSION = '0.1.0'
5
+ end
data/lib/vendi.rb ADDED
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'logger'
4
+ require 'fileutils'
5
+ require 'cardano_wallet'
6
+ require 'vendi/version'
7
+ require 'vendi/utils'
8
+ require 'vendi/monitor'
9
+ require 'vendi/minter'
10
+ require 'vendi/machine'
11
+
12
+ # Vendi CNFT Vending machine
13
+ module Vendi
14
+ # Timeout for tx to get into ledger
15
+ TIMEOUT = 300
16
+
17
+ POLICY_SCRIPT_TEMPLATE = 'cosigner#0'
18
+
19
+ def self.init(wallet_opts = {}, log_level = :info, logfile = nil)
20
+ Vendi::Machine.new(wallet_opts, log_level, logfile)
21
+ end
22
+ end
metadata ADDED
@@ -0,0 +1,118 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: vendi
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Piotr Stachyra
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2022-10-28 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: cardano_wallet
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: 0.3.28
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: 0.3.28
27
+ - !ruby/object:Gem::Dependency
28
+ name: docopt
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: 0.6.1
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: 0.6.1
41
+ - !ruby/object:Gem::Dependency
42
+ name: rake
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - '='
46
+ - !ruby/object:Gem::Version
47
+ version: 12.3.3
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - '='
53
+ - !ruby/object:Gem::Version
54
+ version: 12.3.3
55
+ - !ruby/object:Gem::Dependency
56
+ name: rspec
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - '='
60
+ - !ruby/object:Gem::Version
61
+ version: 3.11.0
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - '='
67
+ - !ruby/object:Gem::Version
68
+ version: 3.11.0
69
+ description: CNFT Vending Machine - cardano-wallet based
70
+ email:
71
+ - piotr.stachyra@gmail.com
72
+ executables:
73
+ - vendi
74
+ extensions: []
75
+ extra_rdoc_files: []
76
+ files:
77
+ - LICENSE.txt
78
+ - README.md
79
+ - bin/console
80
+ - bin/rebuild
81
+ - bin/setup
82
+ - bin/vendi
83
+ - lib/vendi.rb
84
+ - lib/vendi/machine.rb
85
+ - lib/vendi/minter.rb
86
+ - lib/vendi/monitor.rb
87
+ - lib/vendi/utils.rb
88
+ - lib/vendi/version.rb
89
+ homepage: https://github.com/piotr-iohk/vendi
90
+ licenses:
91
+ - MIT
92
+ metadata:
93
+ allowed_push_host: https://rubygems.org/
94
+ homepage_uri: https://github.com/piotr-iohk/vendi
95
+ source_code_uri: https://github.com/piotr-iohk/vendi
96
+ changelog_uri: https://github.com/piotr-iohk/vendi
97
+ rubygems_mfa_required: 'true'
98
+ post_install_message:
99
+ rdoc_options: []
100
+ require_paths:
101
+ - lib
102
+ - bin
103
+ required_ruby_version: !ruby/object:Gem::Requirement
104
+ requirements:
105
+ - - ">="
106
+ - !ruby/object:Gem::Version
107
+ version: 2.7.0
108
+ required_rubygems_version: !ruby/object:Gem::Requirement
109
+ requirements:
110
+ - - ">="
111
+ - !ruby/object:Gem::Version
112
+ version: '0'
113
+ requirements: []
114
+ rubygems_version: 3.3.7
115
+ signing_key:
116
+ specification_version: 4
117
+ summary: CNFT Vending Machine - cardano-wallet based
118
+ test_files: []