Writing your First gRPC client for LND using C#
Ladies and gentlemen, welcome on board to my channel, today, we will be learning some basic lightning implementation using C#.
Please make sure your seat belt is securely fastened, take away every distraction, and let's fly.
The lightning network and all the associated concepts would continue to amaze me with how the system is set up to scale blockchain and enable trustless, instant payments. All these are achieved, and still, the transactions are kept off-chain while leveraging the security of the underlying blockchain as an arbitration layer.
The lightning network has opened a portal whereby different software developers could leverage the web to build solutions that would improve human lives and the environment. There are so many guides, articles, and tutorials that help developers program on the lightning network across several languages, which includes Go, C, Typescript, Scala, Rust, etc. Most of these languages have their heart at the center of Bitcoin and Lightning development, while others aren't, one of which is Microsoft's C#.
Navigating building lightning-based application at the early stage isn't all that smooth. I found the starting process to be incredibly difficult, and I believe it is a general case as there are not so many resources available to guide. This is why I have created this guide to help save a few lost souls some pain and also to help C# developers programming on the lightning network.
P.S: This guide expects that you already know to program and write C# well enough. For people who are just learning about programming, I suggest you leverage platforms that help people get into tech. Youtube is also an excellent resource for this.
We would start with the basic operations, which would include invoice generation, decoding invoices, listening for paid invoices, sending payments, etc. My belief is once you understand the basics, you can move on to writing more complex and secure applications.
Before we begin, what is gRPC? gRPC is a modern open source high performance Remote Procedure Call (RPC) framework used to build scalable and fast APIs in any environment. It allows the client and server applications to communicate transparently and develop connected systems.
WHy is this important? for this test project, we would be connecting our project to LND using gRPC.
First, you must install and set up Bitcoin core and Lightning on your machine. This article gives a step-by-step guide toward setting up Bitcoin core and Lightning. Do well to follow through.
Beyond that, for the sake of this tutorial, you would need to download and install lightning Polar and docker (if you don't have one), as they work hand-in-hand, especially for windows users. Polar provides an interface to perform a good number of your lightning operations for your local and test applications. Beyond that, it also helps set up the development environment so that you can focus more on the building.
Other requirements/tools that would help you follow through this guide would include:
Visual Studio (I used Visual Studio 2022 as at the time of writing this article)
Git bash or any Linux based terminal
By now, you should have polar installed on your computer. All you have to do is to run it, but before you do that, you need to make sure your docker is running, else when you try to run, you most likely will get a 'Docker not detected' modal pop up. For a good guide on setting up Polar for this guide, I would advise you to look through this documentation by lightning labs. Make sure to have at least two nodes set up. You can decide to have more than that, but for the sake of this article, please do have two nodes. Also, go ahead and play around with polar, you can try creating a payment channel between nodes, invoice creation, invoice payment, etc.
Now that you have all your necessary dependencies and have Polar installed and running, it's time to focus on the business for the day.
Go ahead, open visual studio, create a new console project, give it your preferred name, and create it.
Before we write any code, we need to install four packages and download lightning. proto file into our project.
To install the package, right click on your solution and select 'manage nuget packages', once it is open, search and install the following packages:
Grpc.Core
Grpc.Tools
Google.Protobuf
You can also manually install them using 'dotnet add' as follows:
dotnet add package Grpc.Core
dotnet add package Grpc.Tools
dotnet add package Google.Protobuf
Confirm that these packages are installed by checking the installed section of the nuget manager or you can check your .csproj file to confirm.
To download the proto file, we need to create a new folder in the root of your project (name it anything. I named mine Grpc) and then put in the lnd proto files. To do this, open your git bash terminal (to download, click here) or Linux based terminal, navigate to the project directory and run the following command.
mkdir Grpc
curl -o Grpc/lightning.proto -s https://raw.githubusercontent.com/lightningnetwork/lnd/master/lnrpc/lightning.proto
What the above command would do is that it would create a new folder inside the root project called Grpc and then download the lightning.proto file, placing the file inside the Grpc folder.
Once the file is successfully downloaded, the last requirement is to include it in our .csproj file as an item group. As shown below:
As you can see in the above screenshot, we can see the three packages included in the .csproj file also.
If you have gotten to this stage successfully, thumbs up, else, you might want to retrace your steps and see where you got it all wrong.
All that is left to do is to clean solution and then rebuild the project. Once this is done, let's proceed to write some code.
First, let's start by setting up our lightning functionalities in C#. Go ahead and create a new class. I would call the name of my class LightningHelper.cs. You can then go ahead and include the following code in the file:
using Grpc.Core;
namespace LightningRpcTestApp
{
public class LightningHelper
{
private readonly string macaroonPath = @"PATH-TO-ADMIN-MACAROON";
private readonly string tlsCertPath = @"PATH-TO-SSL-CERT";
private readonly string rpcHost = @"RPC-HOST";
public Lnrpc.Lightning.LightningClient UserClient()
{
var channel = new Channel(rpcHost, GetSslCredentials());
var client = new Lnrpc.Lightning.LightningClient(channel);
return client;
}
public SslCredentials GetSslCredentials()
{
Environment.SetEnvironmentVariable("GRPC_SSL_CIPHER_SUITES", "HIGH+ECDSA");
var cert = System.IO.File.ReadAllText(tlsCertPath);
var sslCreds = new SslCredentials(cert);
return sslCreds;
}
public string GetMacaroon()
{
byte[] macaroonBytes = File.ReadAllBytes(macaroonPath);
var macaroon = BitConverter.ToString(macaroonBytes).Replace("-", "");
// hex format stripped of "-" chars
return macaroon;
}
}
}
What are all these?
Let's start from top. The first line of code represent package importation. Due to it's high level of privacy, for us to successfully perform any operation over the lightning invoice, data transmission between nodes on the network has to be very secured. For this to happen, we need the node's macaroon, tls/ssl cert.
Macaroons are advanced authentication mechanism used in distributed systems. They are designed to combine the advantages of the bearer and identity-based authentication systems in a single token that can quickly be issued and verified without requiring access to a central database. You can read more about macaroons here.
TLS/SSL certificate is an integral part of web servers data transmission. It allows web servers communicate securely with clients via some protocols (HTTP) through a method known as handshake. There are two main important role of TLS cert: It establishes trust and genuity, it facilitates secure transmission of information between parties via encryption.
Select one of the nodes (Alice's node in my case), click on the 'connect' section, and the then copy the required files path. For now we are making use of the file path, which we would then use to read the files content. This is what the GetMacaroon() and GetSslCredentials() methods are doing in the code above. Here is a snapshot of what we need from Polar.
In the code above, replace the macaroonPath variable with the admin macaroon path dictated in polar. Do the same for the tlsCertPath and rpcHost in accordance to the highlighted path in polar (green arrow, as shown in the screen above).
The last part of the code is the UserClient method. The main function of that method is to create a channel to Alice node (not a lightning channel).
Now we have lightning connection to our node all setup, lets go to the program.cs file and implement some lightning functionalities.
Wallet Balance
This is a basic command to request the balance of the node.
using Grpc.Core;
using LightningRpcTestApp;
using Lnrpc;
using Newtonsoft.Json;
static void GetWalletBalance()
{
var helper = new LightningHelper();
var client = helper.UserClient();
var response = client.WalletBalance(new WalletBalanceRequest(), new Metadata() { new Metadata.Entry("macaroon", helper.GetMacaroon()) });
Console.WriteLine($"Wallet balance is: {response.TotalBalance}");
}
GetWalletBalance();
So what did we do here?
var helper = new LightningHelper();
We have earlier defined a helper class that that helps us connect our application to our node in Polar. We used this particular line of code to create an instance of the class.
var client = helper.UserClient();
Now that we have created an instance of the class, we can then go ahead and use methods that are available inside the class. One of those methods is the 'UserClient' method which we have said is our channel to connect to our lightning node.
var response = client.WalletBalance(new WalletBalanceRequest(), new Metadata() { new Metadata.Entry("macaroon", helper.GetMacaroon()) });
Now that we have our connections established, we also have available to us several lightning methods courtesy of lnrpc. Since what we want to do is to retrieve the node's balance, we make use of the WalletBalance method available to our client. This method takes in two main parameters: wallet balance request, as well as our macaroon passed in as a metadata. This is to authenticate the connection calls.
If we go ahead and run the application, we would see the balance as shown below:
If we check our polar, we would see the same value (approximated)
Generate Invoice
The next method we are going to look at is generating an invoice (or payment request). Like the name suggest, payment request, we are basically suggesting an amount that we would like to be paid in. So for this method we would have to pass in one parameter, which would be the amount we are requesting for. Code is shown below:
static void CreateInvoice(int satoshis)
{
var helper = new LightningHelper();
var client = helper.UserClient();
var invoice = new Invoice();
invoice.Memo = "A test invoice";
invoice.Value = (long)satoshis;
var invoiceResponse = client.AddInvoice(invoice, new Metadata() { new Metadata.Entry("macaroon", helper.GetMacaroon()) });
Console.WriteLine(invoiceResponse.PaymentRequest);
}
CreateInvoice(200);
For result, we have this:
It is good to point out that every value you see in that long invoice string have a representation. The payment request is usually divided into two segment, the human readable segment and the payment request. These two are seperated by a value '1', which is the 9th character in our invoice. The human readable bit, gives us information about the network choice (lnbcrt => regtest), the value we are sending 2u (2 microsatoshis). I'd recommend that you read more on invoices. You can use this to further your learning.
Decode Invoice
Our third method we would be looking at is the decode invoice method. What this method basically does is to decode a payment request (or invoice), showing more human-readable information about that particular invoice, such as the destination, the value in satoshis, the expiration time amongst others.
static void DecodeInvoice(string paymentRequest)
{
var helper = new LightningHelper();
var client = helper.UserClient();
var paymentReq = new PayReqString();
paymentReq.PayReq = paymentRequest;
var invoiceResponse = client.DecodePayReq(paymentReq, new Metadata() { new Metadata.Entry("macaroon", helper.GetMacaroon()) });
Console.WriteLine(JsonConvert.SerializeObject(invoiceResponse));
}
DecodeInvoice("lnbcrt2u1p3lyhempp53va5utfllaxdap0frrjthppj5ct7gm4amulxsuhtqpqz5mgdvmxsdqhgys8getnwssxjmnkda5kxegcqzpgsp5ka9sdxakel03yrclf9ddxqckyp45thhf8tfa58r3007a9t88nrtq9qy9qsq6kdyvnpp6zar8jumq064r2xa7e5hzzyxq007zhzuqgm6sp5xqsehakd5fy5vttjfedyzlcnkwptslx3vrttnf9mfndz5kz8dme6zymgq4qzdd4");
Pay Invoice
Let's try making payment. Let's go to our Polar interface, right click on Bob's node and select 'create invoice', this would create that we would pay using our application.
The code for paying an invoice is shown below:
static void SendLightning(string paymentRequest)
{
var helper = new LightningHelper();
var client = helper.UserClient();
var sendRequest = new SendRequest();
sendRequest.Amt = 200;
sendRequest.PaymentRequest = paymentRequest;
var response = client.SendPaymentSync(sendRequest, new Metadata() { new Metadata.Entry("macaroon", helper.GetMacaroon()) });
}
Once our application has run successfully, yo can go ahead and confirm that there has been an increase in value (satoshis) for Bob's node.
Listen for settled Invoice
This is the final method we are going to show of the many methods that are available on the lightning network. This method, as the title suggest, listens for when an invoice has been paid. So we generate an invoice using our application, and we give it out to a user to pay for it, this method checks to see when the user has completed payment to that particular result and then informs the system. The code for this is shown below:
static async void ListenForSettledInvoice()
{
string response = default;
var helper = new LightningHelper();
var txnReq = new InvoiceSubscription();
var adminClient = helper.UserClient();
var settledInvoice = adminClient.SubscribeInvoices(txnReq, new Metadata() { new Metadata.Entry("macaroon", helper.GetMacaroon()) });
using (var call = settledInvoice)
{
while (await call.ResponseStream.MoveNext())
{
var invoice = call.ResponseStream.Current;
if (invoice.State == Invoice.Types.InvoiceState.Settled)
{
Console.WriteLine(invoice.ToString());
}
}
}
}
And this
, my friends, is the beginning of greatness in this world of freedom money as a software developer. So far, we have seen how we can perform some lightning implementation using C#. The good thing is that all these methods can be expanded and used to perform more operations and also used to build bigger projects. Here is the full code for people who might be interested.
using Grpc.Core;
using LightningRpcTestApp;
using Lnrpc;
using Newtonsoft.Json;
// Get node balance
static void GetWalletBalance()
{
var helper = new LightningHelper();
var client = helper.UserClient();
var response = client.WalletBalance(new WalletBalanceRequest(), new Metadata() { new Metadata.Entry("macaroon", helper.GetMacaroon()) });
Console.WriteLine($"Wallet balance is: {response.TotalBalance}");
}
// Generate invoice
static void CreateInvoice(int satoshis)
{
var helper = new LightningHelper();
var client = helper.UserClient();
var invoice = new Invoice();
invoice.Memo = "A test invoice";
invoice.Value = (long)satoshis;
var invoiceResponse = client.AddInvoice(invoice, new Metadata() { new Metadata.Entry("macaroon", helper.GetMacaroon()) });
Console.WriteLine(invoiceResponse.PaymentRequest);
}
// Decode invoice
static void DecodeInvoice(string paymentRequest)
{
var helper = new LightningHelper();
var client = helper.UserClient();
var paymentReq = new PayReqString();
paymentReq.PayReq = paymentRequest;
var invoiceResponse = client.DecodePayReq(paymentReq, new Metadata() { new Metadata.Entry("macaroon", helper.GetMacaroon()) });
Console.WriteLine(JsonConvert.SerializeObject(invoiceResponse));
}
// Pay invoice
static void SendLightning(string paymentRequest)
{
var helper = new LightningHelper();
var client = helper.UserClient();
var sendRequest = new SendRequest();
sendRequest.Amt = 200;
sendRequest.PaymentRequest = paymentRequest;
var response = client.SendPaymentSync(sendRequest, new Metadata() { new Metadata.Entry("macaroon", helper.GetMacaroon()) });
}
// Listen for settled payment
static async void ListenForSettledInvoice()
{
string response = default;
var helper = new LightningHelper();
var txnReq = new InvoiceSubscription();
var adminClient = helper.UserClient();
var settledInvoice = adminClient.SubscribeInvoices(txnReq, new Metadata() { new Metadata.Entry("macaroon", helper.GetMacaroon()) });
using (var call = settledInvoice)
{
while (await call.ResponseStream.MoveNext())
{
var invoice = call.ResponseStream.Current;
if (invoice.State == Invoice.Types.InvoiceState.Settled)
{
Console.WriteLine(invoice.ToString());
}
}
}
}
Ladies and gentlemen, welcome to your destination, the flight has landed (safely, I guess).
Just before you go, let me assume that you reading up to this point would mean that you have considered a career in bitcoin development and lightning development and don't know how to go about it. Let me introduce you to Qala
Qala is a program designed to train the next generation of African bitcoin and lightning developers. It goes beyond surface learning and takes you neck deep into the core of bitcoin and lightning development. Qala is a project-based learning program. As a participant, you get to work on diverse projects like open source, personal projects, etc.
Go now and apply to begin your journey in this new exciting world.
until next time/article, Obrigado
Additional resource:
https://github.com/lightningnetwork/lnd/blob/master/docs/grpc/c%23.md
https://github.com/lightningnetwork/lnd/blob/master/lnrpc/README.md