Package: cardano-nft-vending-machine
cardano-nft-vending-machineA simple CNFT mint-and-vend machine Python library that leverages cardano-cli and Blockfrost.io:warning:IMPORTANTInteractions on the Cardano blockchain involveREAL CURRENCY AND SHOULD NOT BE TREATED LIGHTLY.  Some principles:Never send money to an address you don't know and can't validateKeys should be stored on servers that have as little attack surface (e.g.,iptables blacklists) as possibleOpen source software should always be audited independently -- UTSL!There areNO WARRANTIES WHATSOEVER WITH THIS PACKAGE-- use at your own riskQuickstartThis project contains Library bindings that can be installed using the standardwheelmechanism.  See thescript quickstart sectionfor how to run from CLI.Library UsageThe library consists of several Python objects representing the mint process.  The sample below shows how one could run an infinite CNFT vending machine on mainnet for a 10₳ mint (gross of fees and rebates) with their NFT:# There are several sample whitelist implementations in cardano.wt.whitelist or you can implement your own
whitelist = SingleUseWhitelist('/path/to/whitelisted/assets/directory')

# The Mint object below represents your Mint and specifies price, and developer fee in Lovelace (both can be 0)
price = Balance(10000000, Balance.LOVELACE_POLICY)
mint = Mint([price], 1000000, 'addr1_developer', '/path/to/nft/json/metadata', '/path/to/mint/script', '/path/to/mint.skey', whitelist)

# Blockfrost is used in the code to validate where the UTXO sent to the payment address came from
blockfrost_api = BlockfrostApi('<BLOCKFROST_PROJ_ID>', mainnet=True)

# CardanoCli is a wrapper around the cardano-cli command (used as a utility without any interaction with the network)
cardano_cli = CardanoCli(protocol_params='/path/to/protocol.json')

# NftVendingMachine vends NFTs and needs to be called repeatedly (with a 25-vend max) so long as the mint period is open
nft_vending_machine = NftVendingMachine('addr_payment', '/path/to/payment.skey', 'addr_profit', 25, mint, blockfrost_api, cardano_cli, mainnet=True)

# The following simple loop carries the state of already-completed UTXOs to avoid double spending errors and uses a do-wait-check loop
already_completed = set()
while _program_is_running:
    nft_vending_machine.vend('/path/to/output/dir', 'locking_subdir_name', 'metadata_subdir_name', already_completed)
    time.sleep(WAIT_TIMEOUT)main.pyThere is a sample vending machine script that is included in thesrc/directory to show how to invoke the library components.  Use-hto see detailed help or use a command like below:python3 main.py [validate | run] \
            --payment-addr <PAYMENT_ADDR> \
            --payment-sign-key /FULL/PATH/TO/payment.skey \
            --profit-addr <PROFIT_ADDR> \
            (--mint-price <PRICE> <POLICY_ID>)+ \
            --mint-script /FULL/PATH/TO/policy.script \
            --mint-sign-key /FULL/PATH/TO/policy.skey \
            --blockfrost-project <BLOCKFROST_PROJECT_ID> \
            --metadata-dir metadata/ \
            --output-dir output/ \
            --single-vend-max <MAX_SINGLE_VEND> \
            [--vend-randomly] \
            [--no-whitelist | \
              [--single-use-asset-whitelist <WHITELIST_DIR> \
                | --unlimited-asset-whitelist <WHITELIST_DIR> \
                | --wallet-whitelist <WHITELIST_DIR> ] \
            [--dev-fee dev_fee --dev-addr dev_addr] \
            [--bogo threshold additional] \
            [--mainnet]InstallationThis package is available fromPyPIand can be installed usingpip3.  Python <3.8 is currently unsupported at this time.pip3 install cardano-nft-vending-machineScriptsIn thescripts/directory there are several scripts that can be used to help operationalize the vending machine.initialize_whitelist.pyThis file should be used exactly once to initialize an asset-based whitelist for an existing NFT policy withALLof the assets that are currently minted.  Note that theCONSUMED_DIRfolder is created but left empty so that the vending machine (e.g., started withmain.py) can use it during running.usage: initialize_whitelist.py [-h] asset --blockfrost-project BLOCKFROST_PROJECT --consumed-dir CONSUMED_DIR [--mainnet] --policy-id POLICY_ID [--preview] --whitelist-dir WHITELIST_DIR
Initialize an asset-based whitelist for an existing NFT policy
optional arguments:
-h, --help
	show this help message and exit
--blockfrost-project BLOCKFROST_PROJECT
	Blockfrost project ID to use for retrieving chain data
--consumed-dir CONSUMED_DIR
	Local folder where consumed whitelist files will go after processing (MUST NOT YET EXIST)
--mainnet
	Run the initializer against mainnet assets (default is False [preprod])
--policy-id POLICY_ID
	Policy ID of the assets to be whitelisted
--preview
	Run the initializer against preview assets (default is False [preprod])
--whitelist-dir WHITELIST_DIR
	Local folder where whitelist files are stored (MUST NOT YET EXIST)--num-mints-per-wl NUMBER
How many whitelist spots are available for each asset on the whitelistupload_wl_usage.pyThis file should be run continuously during a whitelist mint to upload changes in the assets used for consumption by external parties.  Note that if there are any performance issues (e.g., IOPS throughput) with the local vending machine filesystem it is recommended you not use this file.  It is kept separate from the main vending machine operation to avoid any synchronization or performance issues as it is not critical.usage: upload_wl_usage.py [-h] --old-wl-file OLD_WL_FILE --out-file OUT_FILE --whitelist-dir WHITELIST_DIR [--credentials CREDENTIALS] [--upload-method UPLOAD_METHOD]
Determine if a set of whitelist assets have been recently used in the vending machine
optional arguments:
-h, --help  show this help message and exit
--old-wl-file OLD_WL_FILE
	Most recent run of this program that was uploaded to cloud storage
--out-file OUT_FILE
	Where to store the new used whitelist information if any changes (can be same as --old-wl-file)
--whitelist-dir WHITELIST_DIR
	Local folder where consumed whitelist files have gone after processing by vending machine
--whitelist-dir WHITELIST_DIR
	Local folder where unused whitelist files are stored to be processed by vending machine
--credentials CREDENTIALS
	JSON-formatted application-specific credentials
--upload-method UPLOAD_METHOD
	Mechanism for uploading changes in whitelist files (e.g., CloudFlare)APIsAll API documentation is auto-generated frompydoc3-formatted multi-line strings in the source code.  A mirror ofmasteris hosted onGithub Pages.BuildBuilding this project creates a.whlfile for uploading toPyPIor installing locally on a server.  All output is, by default, stored todist/.  To build, run:python3 -m buildTestTo enhance the output of tests, we recommend installingpytest-clarity:pip3 install pytest-clarityTests are stored in thetestssubdirectory and are run usingpytest.  But before invoking the tests you need to create secrets forblockfrost.ioand fund a test address (the "funder") as below.Blockfrost Key FilesThere are two supported test Cardano networks on Blockfrost as of the time of this writing: preprod and preview.  More information on these two supported test networks can be found on theCardano.org testnet documentation.For running the testing suite on preprod you will need to create a file attests/secrets/blockfrost-preprod.keythat containsjust your blockfrost keyfor preprod. If you want to run on preview, simply do that same for your preview key attests/secrets/blockfrost-preview.key.  For more information, see thefull blockfrost.io documentation.Funder Key FilesThe test suite assumes that there is a "funder" address that is able to pay tADA out (and then receive the entire sum at the end of a successful test less chain fees).  To create a new funder address, run:cardano-cli address key-gen \
	--signing-key-file tests/secrets/funder.skey \
	--verification-key-file tests/secrets/funder.vkeyThe result will be two files (the signing key and verification/public key) of a "funder" address.  This address can be determined at the CLI using:cardano-cli address build \
        --payment-verification-key-file tests/secrets/funder.vkey \
        --testnet-magic <PREPROD_OR_PREVIEW_MAGIC>Before running any tests ensure the funder has adequate tADA by using thetestnets faucetfor whichever test network you will be running the test suite against.Running the TestsFinally, to run the tests:python3 -m pytest -vv [-s] [-k TEST_SINGLE_PATTERN]By default tests will run on thepreprod Cardano network.  To test against mainnet or thepreview Cardano networkyou can use theTEST_ON_MAINNETorTEST_ON_PREVIEWenvironment variables as follows:TEST_ON_PREVIEW=true python3 -m pytest -vvPull requests tomasterrequire 0 failing tests in order to be merged.Scale TestingThe filetests/scale_test.pycontains mechanisms for testing the vending machine at scale which are, by default, skipped during a test run.  To manually invoke these tests, you need to pass several specific parameters.  For example, running:python3 -m pytest -vv -s -k test_concurrent_wallet_usage \
	--available-assets 100
	--max-nfts 4
	--min-nfts 0
	--mint-price 5000000
	--num-wallets 100Would run a simulation where 100 wallets would randomly attempt to purchase between 0 and 4 assets (according to a normal distribution), during a 5A mint.  The code would ensure that each recipient received less than or equal to their stated asset count and that in total no more than 100 assets were minted.Note that currently we have only loaded 100 sample files intotests/data/scalebut could increase this in the future as needed.  The key here is the concurrent wallet/UTxO creation, not necessarily the asset scale.  Whitelist scale testing is not supported at the moment.Code CoverageWe usecoverageto measure code coverage from our pytests across the code base.  To run a full test suite with code coverage metrics, invoke:python3 -m coverage run --source=src/ --branch -m pytest -vvNote that you mustseparatelygenerate the report on your CLI using the following:python3 -m coverage htmlWe aim to maintain 85%+ coverage (lines + branches) if possible.DocumentationDocumentation is stored in multi-line comments inside the source code and should be generated using thepdoc3package as follows:pdoc3 --html -o docs/ src/cardano