How to Integrate Solana Blockchain into C#?

Introduction

In this tutorial, we will learn how to integrate the Solana blockchain with a C# application. Solana is a high-performance blockchain that enables decentralized applications and cryptocurrency. Due to its rapid transaction speeds and minimal costs, it has become a favorite alternative among developers. Using C#, a powerful and versatile programming language, you can create complex apps that connect seamlessly with the Solana network.

Solana is known for its high throughput and low transaction costs, making it ideal for decentralized applications and financial products. To read more about Solana, follow this article: What Is Solana And Why Use Solana?

Solana in C sharp

How to integrate Solana into the C# application?

Integrating Solana with C#, a language known for its robust enterprise applications, allows developers to harness the best of both worlds. We will use Solnet, an unofficial .NET library, to facilitate communication with the Solana network.

Step 1. Setting up the development environment

Before we start, ensure you have the following tools installed:

  • .NET SDK
  • Visual Studio

In this tutorial, I am using .NET 8 and Visual Studio 2022. You can choose your preferred version.

Step 2. Create a C# project

Here, I am creating a new ASP.NET Core MVC project.

Open Visual Studio. Click on Create a new project, In this window, you have the option to choose the type of project you want to create.

Create a new project

Choose "ASP.NET Core Web App (Model-View-Controller)" and click Next. A new window will open to Configure your project.

Configue your new project

Name the project and click Next. In the Additional Information window, select the Framework that you want to use. I am using .NET 8.0.

Additional information

Click Create. Your project will be created, and the Solution Explorer will look like the below-

Solution explorer

Step 3. Install dependencies

Now that the project is set up. Let's install the necessary dependencies.

Go to the NuGet package manager and install the following packages.

  • Solnet.Rpc
  • Solnet.Wallet

Step 4. Set session

In the program.cs, set session. This is the optional step.

builder.Services.AddSession(options =>
{
    options.IdleTimeout = TimeSpan.FromMinutes(30); // Set session timeout
    options.Cookie.HttpOnly = true;
    options.Cookie.IsEssential = true;
});
app.UseSession();

Step 5. Change the default homepage

In the Index.cshtml

@{
    ViewData["Title"] = "Home Page";
}
<div class="text-center">
    <h1 class="display-4">Welcome to C# Solana Integration Application</h1>
</div>

Here, we removed the default code and added our Welcome message.

Step 6. Create service

Let's add a service that will handle all the Solana operations. Right Click on the project, Click Add, and click New Folder. Name the Folder "Services" and press enter.

Now Right Click on the Service Folder. Click Add and Click Class. Name the Class as SolanaServices.cs.

In SolanaServices.cs

using Solnet.Rpc;
using Solnet.Rpc.Models;
using Solnet.Wallet;
using Solnet.Wallet.Bip39;
namespace C_SolanaIntegration.Services
{
    public class SolanaService
    {
        private readonly IRpcClient _rpcClient;

        public SolanaService()
        {
            _rpcClient = ClientFactory.GetClient(Cluster.DevNet);
        }
    }
}

Here, we added the packages to be used in this class later on. The 'SolanaService' class in the 'C_SolanaIntegration.Services' namespace initializes an RPC client connected to the Solana DevNet cluster. This client (`_rpcClient`) will be used for interacting with the Solana blockchain.

Now, let's add methods.

public (Account, string, string) CreateAccount()
{
    var mnemonic = new Mnemonic(WordList.English, WordCount.Twelve);
    var wallet = new Wallet(mnemonic);
    var account = wallet.Account;
    return (account, Convert.ToBase64String(account.PrivateKey.KeyBytes), mnemonic.ToString());
}

The 'CreateAccount' method generates a new Solana wallet using a 12-word mnemonic phrase, returning the Account details, base64-encoded private key, and mnemonic as a tuple. This enables account creation and secure key storage for the user.

public async Task<ulong> GetBalanceAsync(string publicKey)
{
    var balanceResult = await _rpcClient.GetBalanceAsync(new PublicKey(publicKey));
    if (balanceResult.WasSuccessful)
    {
        return balanceResult.Result.Value;
    }
    throw new Exception(balanceResult.Reason);
}

The 'GetBalanceAsync' method fetches the balance of a specified Solana account by its public key using the RPC client. If successful, it returns the account balance; otherwise, it throws an exception with the error reason.

public async Task RequestAirdropAsync(string publicKey, ulong amount)
{
    var airdropResult = await _rpcClient.RequestAirdropAsync(new PublicKey(publicKey), amount);
    if (!airdropResult.WasSuccessful)
    {
        throw new Exception(airdropResult.Reason);
    }
    await ConfirmTransactionAsync(airdropResult.Result);
}
private async Task ConfirmTransactionAsync(string transactionSignature)
{
    const int maxAttempts = 10;
    const int delay = 2000; // 2 seconds delay
    for (int i = 0; i < maxAttempts; i++)
    {
        var confirmation = await _rpcClient.GetSignatureStatusesAsync(new[] { transactionSignature });
        if (confirmation.WasSuccessful && confirmation.Result.Value[0] != null)
        {
            if (confirmation.Result.Value[0].ConfirmationStatus == "confirmed" || 
                confirmation.Result.Value[0].ConfirmationStatus == "finalized")
            {
                return;
            }
        }
        await Task.Delay(delay);
    }
    throw new Exception("Transaction confirmation failed.");
}

The 'RequestAirdropAsync' method requests an airdrop of SOL to a specified Solana account using the RPC client and verifies the success of the airdrop request. If the request is unsuccessful, it throws an exception with the error reason. After requesting the airdrop, it calls the 'ConfirmTransactionAsync' method, which repeatedly checks the status of the transaction for confirmation, with a maximum of 10 attempts and a 2-second delay between each attempt. If the transaction is not confirmed within the allotted attempts, it throws an exception indicating the failure.

public async Task<List<SignatureStatusInfo>> GetTransactionHistoryAsync(string publicKey)
{
    var transactionHistory = await _rpcClient.GetConfirmedSignaturesForAddress2Async(new PublicKey(publicKey));
    if (transactionHistory.WasSuccessful)
    {
        return transactionHistory.Result;
    }
    throw new Exception(transactionHistory.Reason);
}

The 'GetTransactionHistoryAsync' method retrieves the transaction history for a specified Solana account using the RPC client. If successful, it returns a list of transaction signature statuses; otherwise, it throws an exception with the error reason.

You can create more methods if you want to add any additional functionality like sending tokens, etc.

Step 7. Create controller

Now that we created the Service. Let's move to the controller. Right-click on the Controller, Click Add, and Click Controller. A window will open.

Choose an empty controller, name it, and click Create. Here I named it SolanaController.

In SolanaController.cs

using C_SolanaIntegration.Services;
using Microsoft.AspNetCore.Mvc;
namespace C_SolanaIntegration.Controllers
{
    public class SolanaController : Controller
    {
        private readonly SolanaService _solanaService;
        public SolanaController()
        {
            _solanaService = new SolanaService();
        }
    }
}

The 'SolanaController' class in the 'C_SolanaIntegration.Controllers' namespace is an ASP.NET MVC controller. It initializes a 'SolanaService' instance in its constructor, which will be used to handle Solana blockchain-related operations. This setup allows the controller to manage and respond to HTTP requests related to Solana functionality within the application.

 [HttpGet]
 [Route("Solana")]
 public IActionResult Solana()
 {
     return View();
 }

Here, I created a get method with a route as Solana.

Now, let's add a post method that will create an account.

[HttpPost]
public IActionResult CreateAccount()
{
    var (account, privateKey, mnemonic) = _solanaService.CreateAccount();
    HttpContext.Session.SetString("PublicKey", account.PublicKey.Key);
    HttpContext.Session.SetString("PrivateKey", privateKey);
    HttpContext.Session.SetString("Mnemonic", mnemonic);
    ViewBag.PublicKey = account.PublicKey.Key;
    ViewBag.PrivateKey = privateKey;
    ViewBag.Mnemonic = mnemonic;
    return View("Solana");
}

The 'CreateAccount' method generates a new Solana account using the 'SolanaService', storing the public key, private key, and mnemonic in session storage. It then sets these values in the 'ViewBag' and returns the "Solana" view to display the account details.

[HttpPost]
public async Task<IActionResult> CheckBalance(string publicKey)
{
  try
  {
     var balance = await _solanaService.GetBalanceAsync(publicKey);
     ViewBag.Balance = balance;
  }
  catch (Exception ex)
  {
     ViewBag.Error = ex.Message;
  }
  return View("Solana");
}

The 'CheckBalance' method asynchronously retrieves the balance of a specified Solana account using the 'SolanaService'. It stores the balance or any error message in the 'ViewBag' and returns the "Solana" view to display the result.

[HttpPost]
public async Task<IActionResult> RequestAirdrop(string publicKey)
{
    try
    {
        await _solanaService.RequestAirdropAsync(publicKey, 1000000000); // 1 SOL
        ViewBag.AirdropSuccess = true;
    }
    catch (Exception ex)
    {
        ViewBag.Error = ex.Message;
    }
    return View("Solana");
}

The 'RequestAirdrop' method asynchronously requests an airdrop of 1 SOL to the specified Solana account using the 'SolanaService'. It sets a flag in the 'ViewBag' to indicate success or stores any error message and returns the "Solana" view to display the result.

[HttpPost]
public async Task<IActionResult> GetTransactionHistory(string publicKey)
{
    try
    {
        var transactions = await _solanaService.GetTransactionHistoryAsync(publicKey);
        ViewBag.Transactions = transactions;
    }
    catch (Exception ex)
    {
        ViewBag.Error = ex.Message;
    }
    return View("Solana");
}

The 'GetTransactionHistory' method asynchronously retrieves the transaction history for a specified Solana account using the 'SolanaService'. It stores the transactions in the 'ViewBag' or any error message and returns the "Solana" view to display the result.

[HttpPost]
public IActionResult ClearSession()
{
    HttpContext.Session.Clear();
    return RedirectToAction("Solana");
}

The 'ClearSession' method clears all session data, including Solana-related information, and redirects the user back to the "Solana" view. This ensures that sensitive account details are removed from session storage.

Step 8. Create view

To add View for this get method. Right-click on the 'Solana()' method and click on Add View. Name this view as Solana.cshtml.

In Solana.cshtml

@{
    ViewData["Title"] = "Solana Dashboard";
}
<div class="header dashboard  mb-3">
    <div class="h2">Solana Dashboard</div>
    <div class="create-account">
        <form method="post" asp-controller="Solana" asp-action="CreateAccount">
            <button class="btn btn-primary" type="submit">Create Account</button>
        </form>
    </div>
</div>
@if (ViewBag.PublicKey != null)
{
    <div>
        <p>Public Key: @ViewBag.PublicKey</p>
        <p>Private Key: @ViewBag.PrivateKey</p>
        <p>Mnemonic: @ViewBag.Mnemonic</p>
    </div>
}
<div class="check-balance mb-3">
    <form method="post" asp-controller="Solana" asp-action="CheckBalance">
        <label for="publicKey">Public Key:</label>
        <input type="text" style="width: 405px;" id="publicKey" name="publicKey" required>
        <button class="btn btn-primary" type="submit">Check Balance</button>
    </form>
</div>
@if (ViewBag.Balance != null)
{
    <div>
        <p>Balance: @ViewBag.Balance lamports</p>
    </div>
}
<div class="request-airdrop mb-3">
    <form method="post" asp-controller="Solana" asp-action="RequestAirdrop">
        <label for="publicKeyAirdrop">Public Key:</label>
        <input type="text" style="width: 405px;" id="publicKeyAirdrop" name="publicKey" required>
        <button class="btn btn-primary" type="submit">Request Airdrop</button>
    </form>
</div>

@if (ViewBag.AirdropSuccess != null)
{
    <div>
        <p>Airdrop successful!</p>
    </div>
}
@if (ViewBag.Error != null)
{
    <div>
        <p style="color:red;">Error: @ViewBag.Error</p>
    </div>
}
<div class="transaction-history mb-3">
    <form method="post" asp-controller="Solana" asp-action="GetTransactionHistory">
        <label for="publicKeyHistory">Public Key:</label>
        <input type="text" style="width: 405px;" id="publicKeyHistory" name="publicKey" value="@ViewBag.PublicKey" required>
        <button class="btn btn-primary" type="submit">Get Transaction History</button>
    </form>
</div>
@if (ViewBag.Transactions != null && ViewBag.Transactions.Count > 0)
{
    <div>
        <h3>Transaction History</h3>
        <ul>
            @foreach (var tx in ViewBag.Transactions)
            {
                <li>@tx.Signature - @tx.ConfirmationStatus</li>
            }
        </ul>
    </div>
}
<div class="clear-session">
    <form method="post" asp-controller="Solana" asp-action="ClearSession">
        <button class="btn btn-primary" type="submit">Clear Wallet Data</button>
    </form>
</div>

The "Solana" view provides a user interface for interacting with Solana blockchain functionalities. It includes options to create a new account, check balance, request an airdrop, view transaction history, and clear session data. Depending on the action performed, relevant data such as account details, balance, airdrop status, transaction history, and error messages are displayed dynamically using ViewBag. Each section of the dashboard contains a form with input fields for user interaction, and upon submission, the corresponding controller action is triggered. Additionally, there's a "Clear Wallet Data" button to remove stored account information from the session.

In the _Layout.cshtml

<div class="navbar-collapse collapse d-sm-inline-flex justify-content-between">
    <ul class="navbar-nav flex-grow-1">
        <li class="nav-item">
            <a class="nav-link text-dark" asp-area="" asp-controller="Solana" asp-action="Solana">Solana</a>
        </li>
    </ul>
</div>

We added a li to call the Solana get method.

Step 9. Output

When we run the application, our home page will look like this.

Welcome to c sharp solana

Click on Solana from the top navbar. The Solana Dashboard page will open with options to perform different actions.

Solana dashboard

Click on the Create account button on the top right side. This will generate an account. And it's information will look like this.

Click on create account

Enter the public key and click on the 'Check Balance' button to check the balance of the account. It will show in lamports.

Click on check balance

Enter the public key and click on the 'Request Airdrop' button and some lamports will be airdropped in your account. On success, it will show Airdrop successful, and if some issue occurs during an airdrop, it will show the error message.

Click on request airdrop

Enter the public key and click on the 'Get Transaction History' button and some lamports will be airdropped in your account.

Click on get tranction history

Enter the public key and click on the 'Get Transaction History' button and the history of all the transactions with their status will show like the above.

Conclusion

Integrating the Solana blockchain with a C# application offers developers a powerful platform to build decentralized applications and financial products. Leveraging the Solnet library and ASP.NET Core MVC framework, developers can seamlessly interact with Solana's high-performance blockchain, enabling rapid transaction speeds and minimal costs. By following this tutorial, developers can harness the capabilities of Solana while leveraging the familiarity and versatility of C# programming, opening doors to innovative and efficient blockchain solutions.