Cover Image for CardanoSharp: A .NET Developer's Guide to Minting NFTs on Cardano

CardanoSharp: A .NET Developer's Guide to Minting NFTs on Cardano

CardanoSharp

Cardano has an active and vibrant NFT ecosystem with many artists and projects. A quick look at pool.pm shows over 8 million NFTs have been minted on the Cardano blockchain.

In this post, I want to focus on how CardanoSharp can help dotnet developers write code to mint NFTs on Cardano.

Before we start coding, let's define what an NFT is.

What is an NFT?

An NFT (Non-Fungible Token) is a unique digital asset representing ownership or proof of ownership of a specific digital or physical item. The key characteristic of NFTs is their non-fungibility, meaning each NFT is distinct and cannot be interchanged with another NFT on a one-to-one basis. This uniqueness is typically assured by a unique identifier or digital signature within its blockchain policy, making each NFT different from every other.

The Use Cases of NFTs

NFTs are often known as digital art or JPEG, used to speculate by digital art collectors. This is but a single use-case of the technology.

NFTs have a wide range of useful use cases, including but not limited to Asset Tokenization, Supply Chain Management, Identity Verification, Memberships, Intellectual Property/Patents and Certificate of Authenticity verifications.

Those use cases focus on improving business efficiency and promoting trust by verification "don't trust, verify."

Now that we have an idea of what an NFT is let's start minting them.

Minting Policy

We covered Cardano Minting Policies in a previous post so that I won't go into details here. However, we will construct a new policy specific to our NFT.

// restore HD wallet
var seed = "scout always message drill gorilla ...";
var mnemonic = new MnemonicService().Restore(seed);

var rootKey = mnemonic.GetRootKey();

// get the second policy key
// https://developers.cardano.org/docs/governance/cardano-improvement-proposals/cip-1855/
var (priKey, pubKey) = GetKeyPairFromPath($"m/1855'/1815'/1'", rootKey);

// generate the public key hash
var policyKeyHash = HashUtility.Blake2b224(pubKey.Key);

// create a basic native script that has a single key
var nativeScript = NativeScriptBuilder.Create.SetKeyHash(policyKeyHash);

// create an All script. which means, 
// all the conditions specifid by the native script must be met
var scriptBuilder = ScriptAllBuilder.Create.SetScript(nativeScript);

// build the script policy
var scriptPolicy = scriptBuilder.Build();

// get the policy id 
var policyId = scriptPolicy.GetPolicyId();

// this method helps in key deriviation
(PrivateKey, PublicKey) GetKeyPairFromPath(string path, PrivateKey rootKey)
{
    var privateKey = rootKey.Derive(path);
    return (privateKey, privateKey.GetPublicKey(false));
}

In the above code, we are generating a basic native script policy with a signing key derived from path "m/1855'/1815'/1'"

Minting an NFT

Minting is the process of creating a new token on the blockchain. It is very similar to minting a native token, except for fungibility.

Also, metadata is almost always added to the minting transaction to give further attributes to the NFT.

In our example, we will mint a subscription NFT, which is structurally identical to a digital art NFT.

We will follow the same steps outlined in Minting Native Tokens

Get the required network info and UTxO.

We need to query for an available UTxO and get the most up-to-date network tip information. You can use the method described in the first post, A Beginner's Guide to CardanoSharp.

Prepare Transaction Variables

Let's prepare our transaction variables, such as NFT name, UTxO signing keys, and NFT metadata.

The most important part of this step is the token's metadata and how it is structured. This structure is well documented on the Cardano developer's website CIP-0025.

Also, take note of the minting quantity, as it must always be 1. There is no technical limitation to the quantity, which means you can mint duplicates of the same NFT.

In some use cases, that might be desirable, e.g. Minting 100 gold and 20 platinum badges to distribute to members. However, you will lose the uniqueness aspect of an NFT.

💡
Event tickets like concerts and movie premiers often have serial numbers, making the ticket unique. Ensure the design of your NFT's metadata to capture that important detail.
// give the NFT a name, must be unique across the collection
var tokenName = "Membership #0";

// nft metadata
// https://developers.cardano.org/docs/governance/cardano-improvement-proposals/cip-0025/
// we will use version 1
var tokenMeta = new Dictionary<string, object>
{
    {
        tokenName, new
        {
            name = tokenName,
            description = "Yearly Membership",
            image = "ipfs://QmY8JiUTDJXHjKSzsyAmR1XZJgJaKRjEFdUxCjPHsygRWn",
            validTo = "31/12/2024",
            website = "https://sarmaad.com"
        }
    }
};

var policyMeta = new Dictionary<string, object>
{
    { policyId.ToStringHex(), tokenMeta},
    { "version", "1.0"}
};

// transaction metadata
var auxDataBuilder = AuxiliaryDataBuilder.Create.AddMetadata(721, policyMeta);

// sender address where the UTxO is been used
var (utxoPrivKey, utxoPubKey) = GetKeyPairFromPath("m/1852'/1815'/0'/0/0", rootKey);

// get the stake address
var (_, stakePubKey) = GetKeyPairFromPath("m/1852'/1815'/0'/2/0", rootKey);

// sender address/change address
var senderAddress = AddressUtility.GetBaseAddress(utxoPubKey, stakePubKey, NetworkType.Preview);

// recieving address
// in this example, this address belong to the same wallet for demonstration purpose
var receiverAddress = new Address("addr_test1qzw3u57msm6ds6neg0na8guv2rqd8sse5v09k5vjcpzlu0j6xzenalsrvktely96xvqtyv72syeycypeqn4fq42x4xnsdya8t0");

Build the mint transaction.

Let's put the gathered information together and mint the NFT.

var utxo = new
{
    tx_hash = "9703749c12b3bcc733cf8c780042e1b5ac9d77693da3b0350b410b25f0bc1dcc",
    tx_index = 1u,
    address = "addr_test1qzpmvykhq99xlfcccfft27rsntwg778mp37zf5dar75prtz6xzenalsrvktely96xvqtyv72syeycypeqn4fq42x4xnsl48ktw",
    value = 9859582198u
};

var minCoin = 2 * 1000000u;
var currentSlot = 38240177u;

// construct a token bundle builder
var tokenBundleBuilder = TokenBundleBuilder.Create.AddToken(policyId, tokenName.ToBytes(), 1);

// Build transaction body
var transactionBody = TransactionBodyBuilder.Create
    .AddInput(utxo.tx_hash, utxo.tx_index)
    .AddOutput(receiverAddress, minCoin, tokenBundleBuilder, outputPurpose: OutputPurpose.Mint)
    .AddOutput(senderAddress, utxo.value - minCoin, outputPurpose: OutputPurpose.Change)
    .SetMint(tokenBundleBuilder)
    .SetTtl(currentSlot + 1200)
    .SetFee(0);

// adding signing keys to the UTxO and token policy
var witnessSet = TransactionWitnessSetBuilder.Create
       .SetScriptAllNativeScript(scripBuilder)
       .AddVKeyWitness(scriptPubKey, scriptPrivKey)
       .AddVKeyWitness(utxoPubKey, utxoPrivKey);

// building the transaction
var transactionBuilder = TransactionBuilder.Create
    .SetBody(transactionBody)
    .SetWitnesses(witnessSet)
    .SetAuxData(auxDataBuilder);

var transaction = transactionBuilder.Build();

// calculate and update transaction fee
var fee = transaction.CalculateFee();
transactionBody.SetFee(fee);

transaction = transactionBuilder.Build();

// adjusting change output
transaction.TransactionBody.TransactionOutputs
    .Last(x => x.OutputPurpose == OutputPurpose.Change).Value.Coin -= fee;

This step is almost identical to Minting Cardano Native Token

There is a single notable mention of the .SetAuxData(auxDataBuilder), this method will include the token metadata to the transaction

Submit Transaction

Using Koios service to submit the transaction to the Cardano network. Submit Cardano Transaction

//Transaction Id
//bac5386b718774fae4cb608d4990fc17ffef8f294798842d45138a0bd08cc681

Congratulations, you have minted your first NFT.

Let's look at it in Eternl wallet

Eternl wallet

This is how the transaction looks like in the Eternl Transactions tab.

Callout 1: The metadata that we included in the transaction. Notice how the label is 721.

Callout 2: The mint action with the token name and quantity

Now, since we added the "721" label with the correct metadata structure, Eternl was able to classify our new token as an NFT correctly

Here is the NFT filtered list:

Here are the details of the NFT when clicked:

Burning NFTs

NFTs are native tokens on the Cardano blockchain, meaning that if the policy is not locked, new NFTs can be minted and burnt without issues.

The use case of burning an NFT is perhaps to "upgrade" a membership from silver to gold. To "upgrade" it, you would burn the old NFT and mint a new token to replace it. Ideally, you do this operation within a single transaction.

I won't go into details on constructing a burn transaction for an NFT, as it's identical to Burn Native Token on Cardano, which I covered in an earlier post.

Just substitute the token name with the NFT name and adjust the quantity to 1.

Closing Thoughts and Next Steps

In this post, I have covered the very basics of minting NFTs on Cardano with CardanoSharp.

This example is useful for cases where you must mint an NFT and send it to the user's wallet. Like a membership NFT after the user has successfully paid for it. Or the use case of sending an NFT gift to a customer after completing an eCommerce purchase.

This example does not cover a use case where the customer might want to redeem an NFT or upgrade an existing membership. These use cases require coordinating your back end and the wallet front end to sign the transaction successfully.

In future posts, we will cover exactly that use case and how to build our backend to work with various frontend wallets.