4. Minting an NFT

For this query, we assume that the user and backend server have already generated the policyId, policy script, and the metadata necessary for minting NFTs, following CIP-25 (opens in a new tab) and CIP-68 (opens in a new tab) standards. In this example, we will focus on CIP-25.

ℹ️

Example of how to create a CIP-68 NFT can be found on GitHub, see Nicholas Maselli's CIP-68 Showcase (opens in a new tab)

Before proceeding, it’s important to understand the requirements for minting an NFT. Although the overall steps are documented on the Cardano Developer Portal (opens in a new tab), it’s crucial to note that many of these steps are performed off-chain, typically by the application backend.

To successfully mint an NFT, the backend must query specific data from the database to construct the transaction.

The required data includes:

  • Protocol Parameters: These define various blockchain settings necessary for calculating transaction fees and other parameters.

  • UTXOs: The Unspent Transaction Outputs associated with the wallet that will fund the minting transaction.

  • Slot/Block Tip: The current slot or block tip to ensure the transaction is valid within the blockchain context.

4.1 Protocol parameters

        SELECT 
		    ep.epoch_no AS epoch,
		    ep.min_fee_a,
		    ep.min_fee_b,
		    ep.max_block_size,
		    ep.max_tx_size,
		    ep.max_bh_size as max_block_header_size,
		    ep.key_deposit,
		    ep.pool_deposit,
		    ep.max_epoch as e_max,
		    ep.optimal_pool_count as n_opt,
		    ep.influence as a0,
		    ep.monetary_expand_rate as rho,
		    ep.treasury_growth_rate as tau,
		    ep.decentralisation as decentralisation_param,
		    ep.extra_entropy,
		    ep.protocol_major AS protocol_major_ver,
		    ep.protocol_minor AS protocol_minor_ver,
		    ep.min_utxo_value AS min_utxo,
		    ep.min_pool_cost,
		    ep.nonce,
		    cm.costs AS cost_models,
		    ep.price_mem,
		    ep.price_step,
		    ep.max_tx_ex_mem,
		    ep.max_tx_ex_steps,
		    ep.max_block_ex_mem,
		    ep.max_block_ex_steps,
		    ep.max_val_size,
		    ep.collateral_percent,
		    ep.max_collateral_inputs,
		    ep.coins_per_utxo_size,
		  	ep.coins_per_utxo_size as coins_per_utxo_word
		FROM 
		    epoch_param ep
		LEFT JOIN cost_model cm ON cm.id = ep.cost_model_id
		ORDER BY epoch_no DESC
		LIMIT 1
 

Expected results format

 
epoch | min_fee_a | min_fee_b | max_block_size | max_tx_size | max_block_header_size | key_deposit | .....
------+-----------+-----------+----------------+-------------+-----------------------+-------------+------
505   | 44        | 155,381   | 90,112	       | 16,384	     | 1,100	             | 2,000,000   | ....
 
(Note: results trimmed for readability)             

🔎 Looking a little closer, this query reads data from database, without modifying the blockchain itself. It retrieves information about the protocol parameters for the most recent epoch.

SELECT lists the specific columns to retrieve from the database tables.

FROM epoch_param ep specifies the main table to query, epoch_param (opens in a new tab).

LEFT JOIN cost_model cm ON cm.id = ep.cost_model_id joins data from the cost_model (opens in a new tab) table based on matching id values. ORDER BY epoch_no DESC sorts the results by epoch_no in descending order, ie. most recent epoch first. LIMIT 1 restricts the output to only the first row, ie. the most recent epoch's data.

4.2 UTXOs associated with specific address

Let's use address addr1qyvl8lmwpfq7pahuadpjwul9xlj7c0atr5hvu4r3cjsjlfua276aqezvwjsfucvq76welxmytfyt7ug5835wntzzdvgsyxh7q9 as an example for this query.

        SELECT 
			tx.hash AS txhash,
			to2."index" AS tx_out_index,
			to2.value AS value,
			to2.address 
		FROM tx_out to2 
		LEFT JOIN tx_in ti ON ti.tx_out_id = to2.tx_id AND ti.tx_out_index = to2."index"
		JOIN tx ON tx.id = to2.tx_id 
		WHERE 
			ti.id IS NULL
			AND to2.address = 'addr1qyvl8lmwpfq7pahuadpjwul9xlj7c0atr5hvu4r3cjsjlfua276aqezvwjsfucvq76welxmytfyt7ug5835wntzzdvgsyxh7q9'
 

Expected results format

 
txhash	 		|  tx_out_index |  value      | address      
----------------+---------------+-------------+----------------------
8cab5...f367073 | 	2 			| 6,058,505   | addr1qyv...vgsyxh7q9
8cab5...f367073 | 	0			|   1,159,390 | addr1qyv...vgsyxh7q9
5ab5b...d7ba36 	| 	2			|   3,062,150 | addr1qyv...vgsyxh7q9
5ab5b...d7ba36  | 	1			|   1,159,390 | addr1qyv...vgsyxh7q9
 
 
(Note: results trimmed for readability)             

🔎 Looking a little closer, this query returns all unspent transaction outputs (UTXOs) associated with provided address.

FROM tx_out to2 starts by selecting all transaction outputs from the tx_out (opens in a new tab) table and assigns them the alias to2. LEFT JOIN tx_in ti ON ti.tx_out_id = to2.tx_id AND ti.tx_out_index = to2."index" performs a LEFT JOIN with the tx_in (opens in a new tab) table which stores transaction inputs.

The join condition matches tx_out_id and tx_out_index from tx_in (opens in a new tab) to the corresponding tx_id and index from tx_out (opens in a new tab).

A LEFT JOIN ensures that all rows from tx_out (opens in a new tab) are included, even if there's no matching row in tx_in (opens in a new tab).

JOIN tx ON tx.id = to2.tx_id performs an INNER JOIN with the tx (opens in a new tab) table, which stores transactions.

This connects each transaction output to2 to its corresponding transaction tx based on the tx_id.

WHERE ti.id IS NULL is the crucial part for identifying unspent outputs. It filters the results to only include rows where there's NO matching entry in tx_in (opens in a new tab). In other words, these are outputs that haven't been used as inputs in any other transaction yet, hence they are "unspent".

AND to2.address = 'addr1qyvl8lmwpfq7pahuadpjwul9xlj7c0atr5hvu4r3cjsjlfua276aqezvwjsfucvq76welxmytfyt7ug5835wntzzdvgsyxh7q9' further filters the results to only show UTXOs belonging to our specific address.

SELECT tx.hash AS txhash, to2."index" AS tx_out_index, to2.value AS value, to2.address selects the following columns from the filtered results:

  • tx.hash ..the transaction hash where the UTXO was created.
  • to2."index" ...the index of the UTXO within the transaction.
  • to2.value ...the ADA value (or other asset values) associated with the UTXO.
  • to2.address ...the address that owns the UTXO

4.3 Chain Tip

 
        SELECT
		    block.slot_no,
		    block.block_no,
		    block.epoch_no,
		    block.time
		FROM
		    block
		WHERE block.slot_no IS NOT NULL
		ORDER BY
		    block.slot_no DESC
		LIMIT 1;
 

Expected results format

 
slot_no	    | block_no	 | epoch_no | time
------------+------------+----------+------------------------
132,711,492 | 10,734,560 |   504    | 2024-08-21 22:03:03.000
           

🔎 Zooming in, this query retrieves information about the chain-tip. In other words, we are looking for the identifier of the most recently processed block on the network.

SELECT block.slot_no, block.block_no, block.epoch_no, block.time specifies which columns we want to retrieve from the database. It is looking for:

  • block.slot_no ...the slot number in which the block was created.
  • block.block_no ...the block number within its epoch.
  • block.epoch_no ...the epoch number in which the block was created.
  • block.time ...the timestamp when the block was created.

FROM block ...this tells the database that we want to get this information from a table called block.

WHERE block.slot_no IS NOT NULL is a filter ensuring that we only get rows (blocks) where the slot_no is not empty or null.

ORDER BY block.slot_no DESC sorts the results in descending order based on the slot_no. This means the block with the highest slot number (the most recent one) will be at the top.

LIMIT 1 limits the output to only one row. Combined with the sorting, this ensures we only get the most recent block.