A Beginner's Guide to CardanoSharp: Getting Started

Sarmaad AminSarmaad Amin
9 min read

CardanoSharp is an open-source dotnet library that helps bridge the Cardano blockchain and the dotnet ecosystem.

💡
In this series, we will explore library features, starting with primary use-cases like setting up wallets, transactions, policies, token minting and advanced multi-sig transactions.

Wallet Management

A blockchain wallet is a fundamental part of a cryptocurrency, enabling users to securely manage and transact with digital assets on the blockchain.

One of the must-have features of any blockchain library is the ability to create and restore a wallet.

To generate a new wallet seed phrase:

// generate a new wallet with a seed phrase of 24 words
// allowed phrase sizes are: 9, 12, 15, 18, 21, 24
var mnemonic = new MnemonicService().Generate(24);

// to get to the seed phrase, you could print out: mnemonic.Words
// result: 
// scout always message drill gorilla ... etc

To restore a seed phrase:

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

The purpose of the mnemonic is to get the root private key of the wallet so we can generate all other receiving addresses and the stake address.

var rootKey = mnemonic.GetRootKey();

Generating Addresses

Cardano is a UTxO blockchain, which means a wallet could have an unlimited number of receiving addresses controlled by one wallet's root key.

Each address has its own derived public and private key from the wallet root key.

Let's consider the following code:

// get the public and private keys of the first payment address in the 
// first account
var (paymentPrv, paymentPub) = GetKeyPairFromPath("m/1852'/1815'/0'/0/0", rootKey);

// get the public and private keys of the stake of the first account
var (stakePrv, stakePub) = GetKeyPairFromPath("m/1852'/1815'/0'/2/0", rootKey);

// choose the network, eg: Mainnet, Preprod, Preview
var network = NetworkType.Preview;

// generate the address by combining the public keys from payment address,
// stake and the target network.
var paymentAddress = AddressUtility.GetBaseAddress(paymentPub, stakePub, network);

// get the stake address for the account
var stakeAddress = AddressUtility.GetStakeAddress(stakePub, network);

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

The code above will produce the following :

// payment address for account 0 preprod network
addr_test1qzpmvykhq99xlfcccfft27rsntwg778mp37zf5dar75prtz6xzenalsrvktely96xvqtyv72syeycypeqn4fq42x4xnsl48ktw

// stake address for account 0 preprod network
stake_test1updrpve7lcpkt9uljzarxq9jx09gzvjvzqusf65s24r2nfczwgdg2

Derivation Path Explained

I want to explore the derivation path "m/1852'/1815'/0'/0/0" and explain it so you can customise it to your needs.

The derivation path format is: m / purpose' / coin_type' / account' / role / index

  • m: represents the root key in a HD wallet

  • purpose: a constant set to 1852, which reference standards set by CIP1852

  • coin_type: a constant set to 1815, which references the birth year of Ada Lovelace

  • account: the account index. HD wallets support multiple accounts

  • role: this indicates the role of the generated key based on the following:

    • 0: external payment address

    • 1: internal change address, which might be used in receiving change from transactions

    • 2: staking key address

  • index: the index of the key within the role

Understanding this structure will help solve cases like: generating a new payment address to receive a payment per shopping cart order and segregating funds per project using the account parameter.

One of the most interesting points is each account will have its staking key. This can allow funds to be spread across multiple SPOs (stake pool operators).

Funding a Preview Wallet

To continue exploring CardanoSharo, you must fund the wallet with test ADA. The easiest way is to use the Cardano Testnets faucet from the documentation website.

Just choose the correct network and the wallet address.

Accessing the Blockchain

In the following chapters of this post, we will need to interact with the blockchain. For example, we will need to check the balance of a wallet's account, retrieve a list of unspent UTXOs and submit transactions.

There are several ways to do this, like:

💡
I have personally used both services, and I would encourage you to review their capabilities to make sure they meet your project needs!

In this post, I will not focus on any of the above services SDKs. I will be using Postman to demonstrate the usecase.

If you want me to cover more details and usage patterns, do let me know.

Query Account Balance

Using Koios account_info endpoint, let's find out the account balance.

From the response, the account has a balance of 10,000,000,000 Lovelace. Given that 1 ADA equals 1,000,000 Lovelace, the total balance is 10,000 ADA.

The faucet will fund accounts with a 10k ADA for testing and development purposes. It is important to return any unused ADA to the faucet so others can use them.

Query Account UTxOs

Often, you will need to query the blockchain for a list of UTxOs to prepare a new spend transaction. Using Koios account_utxos endpoint will retrieve a list of all available utxos.

💡
The term UTxO stands for unspent transaction output

The details that we are after are tx_hash, tx_index, value and address

Query Network Tip

Transactions in Cardano can be time-sensitive, meaning a transaction not included in a block is cancelled after an elapsed time. It is often used in wallets and DEXs, and I have seen values from 20 minutes to 2 hours. In our case, we will use 20 min.

We are interested in abs_slot as it represents the chain time. In Cardano, each slot length is 1 second.

Create and Submit a Transaction

Using the information gathered above, let's transfer 125 ADA to addr_test1qq35qstudj02zlj0uc2a3l6rnzrhdh636eqqq3uwsfpqgt07v7zx7nd3fjucmfy8wfnn08f20yey9qfvedy5sfzvwzcqex3jy5

Let's examine the setup variables:

// starting from the rootkey
var rootKey = mnemonic.GetRootKey();

// we will use a single-address strategy throughout this example.
// get the change and stake keys 
var (changePrv, changePub) = GetKeyPairFromPath("m/1852'/1815'/0'/0/0", rootKey);
var (stakePrv, stakePub) = GetKeyPairFromPath("m/1852'/1815'/0'/2/0", rootKey);

// generate a change address for Preview network
var changeAddress = AddressUtility.GetBaseAddress(changePub, stakePub, NetworkType.Preview);

//
// prepare transaction details
// those are filled from previous steps
//

// the amount of lovelace in the transaction hash
var amount_lovelace = 10000000000ul;
// the input hash
var inputTx = "edebf0d01def27d4e777b4acb590c51a7b35c87a5f8a612997718d050d9c85dc";
// the index of the input hash
var inputTxIndex = 0u;
// the address of the input hash
var inputAddress = "addr_test1qzpmvykhq99xlfcccfft27rsntwg778mp37zf5dar75prtz6xzenalsrvktely96xvqtyv72syeycypeqn4fq42x4xnsl48ktw";

// how much are we transfering 125,000,000 lovelace - 125 ADA
var amount_transfer_lovelace = 125 * 1000000ul;
// the current slot (blockchain time)
var currentSlot = 32613442u;
// the recipient 
var recipientAddress = new Address("addr_test1qq35qstudj02zlj0uc2a3l6rnzrhdh636eqqq3uwsfpqgt07v7zx7nd3fjucmfy8wfnn08f20yey9qfvedy5sfzvwzcqex3jy5");

Now that we have our required variables available, let us focus on building the transaction:

// construct a transaction body
var transactionBody = TransactionBodyBuilder.Create
    // input tx hash and index position
    .AddInput(inputTx, inputTxIndex)
    // the address we sending ADA and the amount
    .AddOutput(recipientAddress, amount_transfer_lovelace)
    // the change address, any left over ADA, it needs to come back to us
    .AddOutput(changeAddress, amount_lovelace - amount_transfer_lovelace, 
        outputPurpose: OutputPurpose.Change)
    // specify a time out - ~20min
    .SetTtl(currentSlot + 1200)
    // do not specify any fee amount yet.
    .SetFee(0);

In this transaction body, we specify the basic building blocks of a transaction on Cardano. A transaction must consume a transaction hash completely with no leftover balance.

Recall from above the available UTxO:

"tx_hash": "edebf0d01def27d4e777b4acb590c51a7b35c87a5f8a612997718d050d9c85dc",
"tx_index": 0,
"address": "addr_test1qzpmvykhq99xlfcccfft27rsntwg778mp37zf5dar75prtz6xzenalsrvktely96xvqtyv72syeycypeqn4fq42x4xnsl48ktw",
"value": "10000000000"

The value is 10000000000. Therefore, the transaction sum of all input values must equal the sum of all output values

Moving on, add witness keys to validate and sign the transaction:

// adding witness keys
var (addrPrivate, addrPublic) = GetKeyPairFromAddress(inputAddress, stakePub, rootKey);
var witnesses = TransactionWitnessSetBuilder.Create
        .AddVKeyWitness(addrPublic, addrPrivate);
💡
It is important to know the keys that belong to the address that is identified in the transaction hash

Let's work on calculating the fee and building the transaction.

// create a transaction builder
var transactionBuilder = TransactionBuilder.Create
    // set the transaction body
    .SetBody(transactionBody)
    // set the witness keys
    .SetWitnesses(witnesses);

// buid the transaction
var transaction = transactionBuilder.Build();

// calculate the transaction fee
var fee = transaction.CalculateFee();

// set the transaction fee on the body of the transaction
transactionBody.SetFee(fee);
// re-build the transaction
transaction = transactionBuilder.Build();
// re-balance the transaction by deducting the fee from the change output
transaction.TransactionBody.TransactionOutputs
.Last(x => x.OutputPurpose == OutputPurpose.Change).Value.Coin -= fee;

At this point, we have a transaction that is ready to submit to the blockchain.

Using CardanoSharp Koios dotnet SDKs, I will submit the transaction:

// transaction rest client
var txClient = RestService.For<ITransactionClient>("https://preview.koios.rest/api/v0");

// open a memory stream of the binary serialized transaction
using var memory = new MemoryStream(transaction.Serialize());
// submit the transaction
var result = await txClient.Submit(memory);

If the transaction keys are valid, not expired and balanced, the transaction will be submitted to the blockchain node to be included in the next block. The result object will return the transaction hash.

If there are any issues, the result variable will be an Error object with a message to help you debug the issue.

Closing Thoughts and Next Steps

In this post, I have taken you through exploring the basic features of CardanoSharp. For dotnet developers, this library simplifies the complexities inherent in blockchain operations. Whether it's generating a wallet, managing transactions, or navigating through the layers of Cardano's blockchain, CardanoSharp offers a dependable and efficient framework. Its ability to lower the entry barrier allows developers to focus on innovation rather than the intricacies of blockchain protocols.

Cardano itself is more than just a platform; it's a thriving ecosystem that promises a more secure, scalable, and sustainable environment for developing decentralized applications. As an open-source project grounded in peer-reviewed research, it presents a fertile ground for developers seeking to leverage blockchain for real-world applications.

The potential possibilities that arise from combining the robustness of Cardano with the versatility of the .NET are vast. We're on the cusp of a new era where blockchain is not just a buzzword but a foundational technology that can address some of the most pressing challenges of our digital age. CardanoSharp is a key that unlocks this potential, providing .NET developers with the means to contribute to a more equitable and efficient digital future. As the community around these technologies grows, so too will the opportunities for innovation, collaboration, and transformation.

Next up in CardanoSharp: we'll mint native tokens, craft NFTs, and enable multi-sig transactions—unlocking Cardano's full potential for .NET developers.

0
Subscribe to my newsletter

Read articles from Sarmaad Amin directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Sarmaad Amin
Sarmaad Amin

I started my journey in programming when I was just 8 years old, writing my first lines of code in BASIC. This early experience sparked a deep love for programming in me, leading me to land my first paid job as a developer at the age of 16. In this role, I built desktop applications using FoxPro, marking the beginning of a rewarding career path. Fast forward a couple of decades, and I am now a full-time full-stack developer and solutions architect. My work involves building web applications, mobile applications, and focusing on backend automation and integration. I've had the opportunity to work with various technology stacks, which has greatly broadened my skill set and understanding of different programming environments. Throughout my career, I've witnessed the evolution of technology and have adapted to these changes, continually updating my skills and knowledge. My journey from a young coding enthusiast to a professional in the field is a testament to my passion for programming and my commitment to this ever-evolving industry.