Bitcoin Cash payments using C# and .NET

0 354
Avatar for thanah85
2 years ago

Part 0 - Introduction

Earlier this year I set about integrating Bitcoin Cash payments into one of my pet projects. I wanted to create a system that would allow my users to renew their subscription at any time for any duration without being required to provide their credit card information or any other potentially sensitive private data, and Bitcoin Cash seemed to me to be the perfect tool for the job. I'm happy to report the implementation was successful and the new subscription system seems to be working great. You can read more about this particular use-case here.

I'm writing this article/tutorial because the lack of articles like it meant I was doing a lot of trial-and-error learning during my integration work. My hope is that this post will be a valuable resource for other developers interested in building Bitcoin Cash into their .NET applications.

Part 1 - Project creation

For this post I'm going to be building a simple C# console application in .NET 5 using Visual Studio 2019.

We'll need two NuGet packages:

NBitcoin is an excellent library that abstracts away a lot of the heavy lifting involved in programming with cryptocurrency. Bitcoin Cash is just one of the blockchains they support.

Part 2 - Private Keys and Public Addresses

Our first task is to create a brand new Bitcoin Cash private key and use that key to generate a public address. Using NBitcoin, we can do that with just a few lines of code:

using NBitcoin;
using NBitcoin.Altcoins;
using System;

namespace bch_demo
{
    class Program
    {
        static void Main()
        {
            var network = BCash.Instance.Mainnet;

            var rawPrivateKey = new Key();
            var privateKey = rawPrivateKey.GetBitcoinSecret(network);
            var address = privateKey.GetAddress(ScriptPubKeyType.Legacy);

            Console.WriteLine($"Private Key: {privateKey}");
            Console.WriteLine($"Public Address: {address}");
        }
    }
}

Running this code will generate and display your new private key and public address. You'll need both later so please copy-paste them somewhere handy.

To prove that we've created a valid Bitcoin Cash address, go ahead and try sending a few cents of BCH to your new public address. Once sent, you should immediately be able to view your transaction in a Bitcoin Cash block explorer by searching for your public address. Here are links to the address I just created and the $0.10 transaction I sent to it:

https://blockchair.com/bitcoin-cash/address/qzpyt8429an598407chjgdvdzhjnly8tvqaf59zath

https://blockchair.com/bitcoin-cash/transaction/1f0a7315e6151f97013368536ba4bbe70a511c33c0316026803448cc747cb4b5

Note - Each BCH private key can create exactly one public address. If you need multiple public addresses, you will need to create one private key for each. XPUBs can be used to create a functionally infinite number of public addresses from a single parent, but that's outside our scope here.

Part 3 - Simple Send

Now that we've created a public address and sent some coins to it, our next milestone will be to send those coins back.

To that end, we need to construct, sign, and broadcast a valid Bitcoin Cash transaction. Each transaction (even the more complicated ones we'll do later) is going to have five things: from address, to address, inputs, outputs, and a signature. Let's tackle them one at a time.

// from address
var privateKey = new BitcoinSecret("KySKmzUTeh1npacwiPKanfSyU1cW1z6qZmt7MBbfFSrdcXWedbyQ", network);
var address = privateKey.GetAddress(ScriptPubKeyType.Legacy);

The 'from address' is where the coins we want to send are right now. In this case, we want to send the coins we sent to our console app at the end of part 2, so we'll use our private key to derive our public 'from address'.

// to address
var toAddress = BitcoinAddress.Create("bitcoincash:qp0vr52smy2tmawtcvqalkrhurdu2mvja5qpmtlpzj", network);

The 'to address' is the public address we want to send the coins to. Here I've simply put in the public address for my bitcoin.com wallet app. I used this same wallet app to send the coins to the console app in the first place, so after this send the coins should end up back where they started. Obviously you'll want to put your own address in here (though I won't complain if you don't lol).

// create transaction
var transaction = Transaction.Create(network);

Here we create the new (blank) transaction so we can start filling it out.

// input
var txid = "1f0a7315e6151f97013368536ba4bbe70a511c33c0316026803448cc747cb4b5";
uint index = 0;

var outPointToSpend = OutPoint.Parse($"{txid}:{index}");

var txin = new TxIn()
{
    PrevOut = outPointToSpend,
    ScriptSig = address.ScriptPubKey
};

transaction.Inputs.Add(txin);

The transaction inputs specify exactly which of the (potentially very many) utxos (unspent transaction outputs) associated with the 'from address' should be used to provide the coins we want to send.

Here I've set 'txid' to the id of the transaction I sent at the end of part 2, which I got by simply looking at my public address in a block explorer and copy/pasting the id from the only transaction in that addresses history.

A transaction can have multiple inputs and multiple outputs, so the 'index' value is used to specify which output of the referenced transaction is to be used as an input for the transaction we're creating.

Note - For this tutorial, the index for your transaction is almost certainly 0. If you want to double-check, view the API output from the block-explorer, find the utxo under 'outputs' and get the 'index' property from there.

So the transaction ID and the index are combined in an OutPoint to precisely identify which coins on the Bitcoin Cash blockchain we want to move with the transaction we're creating.

The OutPoint and the ScriptSig of the 'from address' are combined to create a transaction input, and that is added to the transaction we're building.

// output
var inputAmount = 13454;
var minerFee = 600;
var sendAmount = inputAmount - minerFee;
var sendMoney = new Money(sendAmount, MoneyUnit.Satoshi);

transaction.Outputs.Add(sendMoney, toAddress.ScriptPubKey);

The transaction outputs describe exactly where these coins are going and how many we want to send.

The input amount is the exact number of coins in the utxo, so I got this number manually by looking at the transaction on the blockchain.

The miner fee I'm using here is probably excessive ($0.004 at the time of writing). You could almost certainly get by with a smaller value, and as time goes on and network conditions change, you may need to revisit this number (or come up with a clever way to set it programmatically).

Finally the send amount is simply the amount of coins in the input minus the miner fee. That value is used to create a Money object, which is combined with the ScriptSig of the 'to address' and added as an output to the transaction.

Notice that we do not need to explicitly specify an output for the miner fee. Miners automatically receive whatever is leftover when the outputs are subtracted from the inputs.

// sign
var txInId = uint256.Parse(txid);
var inputMoney = new Money(inputAmount, MoneyUnit.Satoshi);
var coin = new Coin(txInId, index, inputMoney, address.ScriptPubKey);

transaction.Sign(privateKey, coin);

The signature proves that we have the authority to move these coins since its not possible to generate a valid signature without knowing the private key. The 'Sign' method accepts the private key and tx-info we've assembled and uses them to append the signature to the transaction.

// broadcast
using (var node = Node.Connect(network, "seed.bchd.cash:8333"))
{
    node.VersionHandshake();

    node.SendMessage(new InvPayload(InventoryType.MSG_TX, transaction.GetHash()));

    node.SendMessage(new TxPayload(transaction));

    Thread.Sleep(5000);
}

Finally we broadcast our transaction to the Bitcoin Cash network. We can see our transaction in a block explorer immediately:

https://blockchair.com/bitcoin-cash/transaction/d6e721893be049e3dbdcb25c44e609cf8bd20906de7e83b88202edec120c4c46

Zooming out, our console app at the end of part 3 looks like this:

using NBitcoin;
using NBitcoin.Altcoins;
using NBitcoin.Protocol;
using System.Threading;

namespace bch_demo
{
    class Program
    {
        static void Main()
        {
            var network = BCash.Instance.Mainnet;

            // from address
            var privateKey = new BitcoinSecret("KySKmzUTeh1npacwiPKanfSyU1cW1z6qZmt7MBbfFSrdcXWedbyQ", network);
            var address = privateKey.GetAddress(ScriptPubKeyType.Legacy);

            // to address
            var toAddress = BitcoinAddress.Create("bitcoincash:qp0vr52smy2tmawtcvqalkrhurdu2mvja5qpmtlpzj", network);

            // create transaction
            var transaction = Transaction.Create(network);

            // input
            var txid = "1f0a7315e6151f97013368536ba4bbe70a511c33c0316026803448cc747cb4b5";
            uint index = 0;

            var outPointToSpend = OutPoint.Parse($"{txid}:{index}");

            var txin = new TxIn()
            {
                PrevOut = outPointToSpend,
                ScriptSig = address.ScriptPubKey
            };

            transaction.Inputs.Add(txin);

            // output
            var inputAmount = 13454;
            var minerFee = 600;
            var sendAmount = inputAmount - minerFee;
            var sendMoney = new Money(sendAmount, MoneyUnit.Satoshi);

            transaction.Outputs.Add(sendMoney, toAddress.ScriptPubKey);

            // sign
            var txInId = uint256.Parse(txid);
            var inputMoney = new Money(inputAmount, MoneyUnit.Satoshi);
            var coin = new Coin(txInId, index, inputMoney, address.ScriptPubKey);

            transaction.Sign(privateKey, coin);

            // broadcast
            using (var node = Node.Connect(network, "seed.bchd.cash:8333"))
            {
                node.VersionHandshake();

                node.SendMessage(new InvPayload(InventoryType.MSG_TX, transaction.GetHash()));

                node.SendMessage(new TxPayload(transaction));

                Thread.Sleep(5000);
            }
        }
    }
}

Part 4 - Block Explorer API Integration

Okay sweet, we're sending Bitcoin Cash using C#. Rock on.

However, one obvious problem with what we've done so far is that we had to manually lookup and hard-code no fewer than three pieces of information about the utxo we used in the transaction. That's icky. Let's fetch those values programmatically from a public BCH block explorer API.

So far part 4, I've sent more coins to the console app's public address, and the milestone for this section will be to send those coins back to my wallet app without manually entering the utxo info.

To that end, we're going to create two new classes for our app.

public class ApiClient
{
    // https://blockchair.com/api/docs
    private readonly string _baseUrl = "https://api.blockchair.com/bitcoin-cash";

    public List<utxo> GetUtxos(string address)
    {
        var client = new HttpClient();

        var response = client.GetAsync($"{_baseUrl}/dashboards/address/{address}").Result;

        var jo = JObject.Parse(response.Content.ReadAsStringAsync().Result);

        var utxos = jo["data"][address]["utxo"].ToString();

        return JsonConvert.DeserializeObject<List<utxo>>(utxos);
    }
}

This first class is a simple utility that accepts a bitcoin cash address and calls an endpoint on the Blockchair API to ask for all the utxos associated with that address. The utility will parse the response and return a list of utxo objects. Speaking of which...

public class utxo
{
    public uint block_id { get; set; }
    public string address { get; set; }
    public string transaction_hash { get; set; }
    public uint index { get; set; }
    public uint value { get; set; }
}

This second class simply describes the format of the utxo objects that the api sends back so the JSON in the response can be deserialized into a list of C# objects.

With these two classes in place, we need to make only minor changes to the existing code. Before the transaction creation, we'll use our new utility to fetch the list of utxos from the BCH blockchain...

// get utxo
var apiClient = new ApiClient();
var utxo = apiClient.GetUtxos(address.ToString()).FirstOrDefault();

if (utxo == null)
    return;

...and then it's simply a matter of plugging the values into the right spots:

// input
var txid = utxo.transaction_hash;
uint index = utxo.index; 
// output
var inputAmount = utxo.value;

Running this modified code returns the coins I sent at the start of this section back to my wallet:

https://blockchair.com/bitcoin-cash/transaction/d833661c7a06c335cbb1f1d34eed2d076411599ac38f2b224141f87156a2cc32

Here's the complete code at the end of part 4:

using NBitcoin;
using NBitcoin.Altcoins;
using NBitcoin.Protocol;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Threading;

namespace bch_demo
{
    class Program
    {
        static void Main()
        {
            var network = BCash.Instance.Mainnet;

            // from address
            var privateKey = new BitcoinSecret("KySKmzUTeh1npacwiPKanfSyU1cW1z6qZmt7MBbfFSrdcXWedbyQ", network);
            var address = privateKey.GetAddress(ScriptPubKeyType.Legacy);

            // to address
            var toAddress = BitcoinAddress.Create("bitcoincash:qp0vr52smy2tmawtcvqalkrhurdu2mvja5qpmtlpzj", network);

            // get utxo
            var apiClient = new ApiClient();
            var utxo = apiClient.GetUtxos(address.ToString()).FirstOrDefault();

            if (utxo == null)
                return;

            // create transaction
            var transaction = Transaction.Create(network);

            // input
            var txid = utxo.transaction_hash;
            uint index = utxo.index;

            var outPointToSpend = OutPoint.Parse($"{txid}:{index}");

            var txin = new TxIn()
            {
                PrevOut = outPointToSpend,
                ScriptSig = address.ScriptPubKey
            };

            transaction.Inputs.Add(txin);

            // output
            var inputAmount = utxo.value;
            var minerFee = 600;
            var sendAmount = inputAmount - minerFee;
            var sendMoney = new Money(sendAmount, MoneyUnit.Satoshi);

            transaction.Outputs.Add(sendMoney, toAddress.ScriptPubKey);

            // sign
            var txInId = uint256.Parse(txid);
            var inputMoney = new Money(inputAmount, MoneyUnit.Satoshi);
            var coin = new Coin(txInId, index, inputMoney, address.ScriptPubKey);

            transaction.Sign(privateKey, coin);

            // broadcast
            using (var node = Node.Connect(network, "seed.bchd.cash:8333"))
            {
                node.VersionHandshake();

                node.SendMessage(new InvPayload(InventoryType.MSG_TX, transaction.GetHash()));

                node.SendMessage(new TxPayload(transaction));

                Thread.Sleep(5000);
            }
        }

        public class ApiClient
        {
            // https://blockchair.com/api/docs
            private readonly string _baseUrl = "https://api.blockchair.com/bitcoin-cash";

            public List<utxo> GetUtxos(string address)
            {
                var client = new HttpClient();

                var response = client.GetAsync($"{_baseUrl}/dashboards/address/{address}").Result;

                var jo = JObject.Parse(response.Content.ReadAsStringAsync().Result);

                var utxos = jo["data"][address]["utxo"].ToString();

                return JsonConvert.DeserializeObject<List<utxo>>(utxos);
            }
        }

        public class utxo
        {
            public uint block_id { get; set; }
            public string address { get; set; }
            public string transaction_hash { get; set; }
            public uint index { get; set; }
            public uint value { get; set; }
        }
    }
}

Part 5 - Complex Send, Multiple Inputs

Both of the transactions we've done so far have been as simple as possible - 1 input and 1 output. But what if you want to bundle a whole bunch of inputs into a single transaction? Maybe you have a donation address that people have sent 20 tips to and now you want to send all those tips to your main wallet. It would be lame to broadcast 20 transactions for that; fortunately we can do that in a single transaction by converting all the utxos from all those tips into inputs.

For part 5, I've sent two transactions from my wallet app to the console address (here and here), and the milestone for this section will be to broadcast a transaction that sends all coins from both transactions back to my wallet app in such a way that it would work regardless of how many utxos we're dealing with (2, 20, 200, whatever).

To do this, we need to make four small changes.

First, let's get all address utxos from the api instead of just the first one:

// get utxos
var apiClient = new ApiClient();
var utxos = apiClient.GetUtxos(address.ToString());

if (utxos.Count == 0)
    return;

Second, we'll use a for loop to create a transaction input for each of the utxos we found in the address:

// inputs
foreach (var utxo in utxos)
{
    var outPointToSpend = OutPoint.Parse($"{utxo.transaction_hash}:{utxo.index}");
    transaction.Inputs.Add(new TxIn()
    {
        PrevOut = outPointToSpend,
        ScriptSig = address.ScriptPubKey
    });
}

Third, the input amount will be equal to the sum of all utxos, and the miner fee will increase with the complexity of the transaction (this is a ham-fisted way calculating a fee, but it works):

// output
var inputAmount = utxos.Sum(u => u.value);
var minerFee = 600 * transaction.Inputs.Count;
var sendAmount = inputAmount - minerFee;
var sendMoney = new Money(sendAmount, MoneyUnit.Satoshi);

transaction.Outputs.Add(sendMoney, toAddress.ScriptPubKey);

Finally, we'll use another for loop to send a list of Coins (rather than a single Coin) into the signing method:

// sign
var coins = new List<Coin>();

foreach (var utxo in utxos)
{
    var txInId = uint256.Parse(utxo.transaction_hash);
    var txAmount = new Money(utxo.value, MoneyUnit.Satoshi);
    var inCoin = new Coin(txInId, utxo.index, txAmount, address.ScriptPubKey);
    coins.Add(inCoin);
}

transaction.Sign(privateKey, coins);

With those changes in place, running the app again sends all the coins from both of the original transactions back to my wallet app, seen here:

https://blockchair.com/bitcoin-cash/transaction/932ceec7433ff3c21ac2d585d4d189ab1fee6b1998d2f70bf316013aa0b8ee87

Here's the complete code at the end of part 5:

using NBitcoin;
using NBitcoin.Altcoins;
using NBitcoin.Protocol;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Threading;

namespace bch_demo
{
    class Program
    {
        static void Main()
        {
            var network = BCash.Instance.Mainnet;

            // from address
            var privateKey = new BitcoinSecret("KySKmzUTeh1npacwiPKanfSyU1cW1z6qZmt7MBbfFSrdcXWedbyQ", network);
            var address = privateKey.GetAddress(ScriptPubKeyType.Legacy);

            // to address
            var toAddress = BitcoinAddress.Create("bitcoincash:qp0vr52smy2tmawtcvqalkrhurdu2mvja5qpmtlpzj", network);

            // get utxos
            var apiClient = new ApiClient();
            var utxos = apiClient.GetUtxos(address.ToString());

            if (utxos.Count == 0)
                return;

            // create transaction
            var transaction = Transaction.Create(network);

            // inputs
            foreach (var utxo in utxos)
            {
                var outPointToSpend = OutPoint.Parse($"{utxo.transaction_hash}:{utxo.index}");
                transaction.Inputs.Add(new TxIn()
                {
                    PrevOut = outPointToSpend,
                    ScriptSig = address.ScriptPubKey
                });
            }

            // output
            var inputAmount = utxos.Sum(u => u.value);
            var minerFee = 600 * transaction.Inputs.Count;
            var sendAmount = inputAmount - minerFee;
            var sendMoney = new Money(sendAmount, MoneyUnit.Satoshi);

            transaction.Outputs.Add(sendMoney, toAddress.ScriptPubKey);

            // sign
            var coins = new List<Coin>();

            foreach (var utxo in utxos)
            {
                var txInId = uint256.Parse(utxo.transaction_hash);
                var txAmount = new Money(utxo.value, MoneyUnit.Satoshi);
                var inCoin = new Coin(txInId, utxo.index, txAmount, address.ScriptPubKey);
                coins.Add(inCoin);
            }

            transaction.Sign(privateKey, coins);

            // broadcast
            using (var node = Node.Connect(network, "seed.bchd.cash:8333"))
            {
                node.VersionHandshake();

                node.SendMessage(new InvPayload(InventoryType.MSG_TX, transaction.GetHash()));

                node.SendMessage(new TxPayload(transaction));

                Thread.Sleep(5000);
            }
        }

        public class ApiClient
        {
            // https://blockchair.com/api/docs
            private readonly string _baseUrl = "https://api.blockchair.com/bitcoin-cash";

            public List<utxo> GetUtxos(string address)
            {
                var client = new HttpClient();

                var response = client.GetAsync($"{_baseUrl}/dashboards/address/{address}").Result;

                var jo = JObject.Parse(response.Content.ReadAsStringAsync().Result);

                var utxos = jo["data"][address]["utxo"].ToString();

                return JsonConvert.DeserializeObject<List<utxo>>(utxos);
            }
        }

        public class utxo
        {
            public uint block_id { get; set; }
            public string address { get; set; }
            public string transaction_hash { get; set; }
            public uint index { get; set; }
            public uint value { get; set; }
        }
    }
}

Part 6 - Complex Send, Multiple Outputs

All of the transactions we've created so far have sent all of the coins in our utxos to the receiving address. That's great as long as you want to send your entire wallet balance or the amount you want to send happens to be exactly equal to the sum of some combination of your utxos. Since both of those of those scenarios are rare (to put it mildly), we need to talk about transactions with multiple outputs.

Using multiple outputs in your transaction gives you the ability to send a portion of the coins in a utxo to the destination address and then send of the rest of them back to yourself in the form of a new utxo. This effectively allows you to send any number of your coins in a transaction.

So for this section, I've sent 3 new transactions to the console address (here, here, and here), and the goal for this segment will be to modify the code so that whenever the app is run, it will send exactly half of the current address balance back to my wallet app. Of course "half" is just an arbitrary amount; you could tweak this to send whatever amount you want.

To make this magic happen, we have a few jobs to do.

First, we'll move the calculation for the "send amount" up so that it occurs right after the utxo fetch, and change the calculation so it gives us 50% of the current address balance.

Second, we need to decide which utxos we're going to use for the transaction. We don't need all of them; we only need enough to cover the send amount. We'll use a loop to scoop utxos into our bucket until we have enough:

// select utxos to cover send amount
var sendAmount = allUtxos.Sum(a => a.value) / 2;

var utxos = new List<utxo>();
foreach (var utxo in allUtxos)
{
    if (utxos.Sum(u => u.value) >= sendAmount)
        break;

    utxos.Add(utxo);
}

The rest of our changes are in the outputs:

//outputs
var utxoTotal = utxos.Sum(u => u.value);

var inputMoney = new Money(utxoTotal, MoneyUnit.Satoshi);
var sendMoney = new Money(sendAmount, MoneyUnit.Satoshi);
var minerFee = new Money(600 * transaction.Inputs.Count, MoneyUnit.Satoshi);

if (sendMoney + minerFee > inputMoney)
    sendMoney = inputMoney - minerFee;

transaction.Outputs.Add(sendMoney, toAddress.ScriptPubKey);

var changeMoney = inputMoney - sendMoney - minerFee;

if (changeMoney.Satoshi > 0)
    transaction.Outputs.Add(changeMoney, address.ScriptPubKey);

There are several things to talk about here.

  • The input total is the sum of just the utxos we selected, not all the utxos in the address (unlike in previous sections).

  • I've added a check to ensure that the amount being sent and the amount going to miners are together less than or equal to the amount being input from the utxos. Honestly this should have been in here earlier, but what can you do?

  • We only add an output for change if there is change. A 0-amount output for change will render the transaction invalid.

  • Notice that the script pub key we're using for the change output is for the "from" address. So when this transaction is sent, these coins will land back in the original address in the form of a newly created utxo for the change amount.

When we run the code with these changes, 50% of the BCH I sent at the start of this section finds its way back into my wallet app:

https://blockchair.com/bitcoin-cash/transaction/ee8d6c42d1bdbeefc3394540a51f7da9aa556c85424030f0d90921f7b90af569

Here's the code at the end of part 6:

using NBitcoin;
using NBitcoin.Altcoins;
using NBitcoin.Protocol;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Threading;

namespace bch_demo
{
    class Program
    {
        static void Main()
        {
            var network = BCash.Instance.Mainnet;

            // from address
            var privateKey = new BitcoinSecret("KySKmzUTeh1npacwiPKanfSyU1cW1z6qZmt7MBbfFSrdcXWedbyQ", network);
            var address = privateKey.GetAddress(ScriptPubKeyType.Legacy);

            // to address
            var toAddress = BitcoinAddress.Create("bitcoincash:qp0vr52smy2tmawtcvqalkrhurdu2mvja5qpmtlpzj", network);

            // get utxos
            var apiClient = new ApiClient();
            var allUtxos = apiClient.GetUtxos(address.ToString());

            if (allUtxos.Count == 0)
                return;

            // select utxos to cover send amount
            var sendAmount = allUtxos.Sum(a => a.value) / 2;

            var utxos = new List<utxo>();
            foreach (var utxo in allUtxos)
            {
                if (utxos.Sum(u => u.value) >= sendAmount)
                    break;

                utxos.Add(utxo);
            }

            // create transaction
            var transaction = Transaction.Create(network);

            // inputs
            foreach (var utxo in utxos)
            {
                var outPointToSpend = OutPoint.Parse($"{utxo.transaction_hash}:{utxo.index}");
                transaction.Inputs.Add(new TxIn()
                {
                    PrevOut = outPointToSpend,
                    ScriptSig = address.ScriptPubKey
                });
            }

            //outputs
            var utxoTotal = utxos.Sum(u => u.value);

            var inputMoney = new Money(utxoTotal, MoneyUnit.Satoshi);
            var sendMoney = new Money(sendAmount, MoneyUnit.Satoshi);
            var minerFee = new Money(600 * transaction.Inputs.Count, MoneyUnit.Satoshi);

            if (sendMoney + minerFee > inputMoney)
                sendMoney = inputMoney - minerFee;

            transaction.Outputs.Add(sendMoney, toAddress.ScriptPubKey);

            var changeMoney = inputMoney - sendMoney - minerFee;

            if (changeMoney.Satoshi > 0)
                transaction.Outputs.Add(changeMoney, address.ScriptPubKey);

            // sign
            var coins = new List<Coin>();

            foreach (var utxo in utxos)
            {
                var txInId = uint256.Parse(utxo.transaction_hash);
                var txAmount = new Money(utxo.value, MoneyUnit.Satoshi);
                var inCoin = new Coin(txInId, utxo.index, txAmount, address.ScriptPubKey);
                coins.Add(inCoin);
            }

            transaction.Sign(privateKey, coins);

            // broadcast
            using (var node = Node.Connect(network, "seed.bchd.cash:8333"))
            {
                node.VersionHandshake();

                node.SendMessage(new InvPayload(InventoryType.MSG_TX, transaction.GetHash()));

                node.SendMessage(new TxPayload(transaction));

                Thread.Sleep(5000);
            }
        }

        public class ApiClient
        {
            // https://blockchair.com/api/docs
            private readonly string _baseUrl = "https://api.blockchair.com/bitcoin-cash";

            public List<utxo> GetUtxos(string address)
            {
                var client = new HttpClient();

                var response = client.GetAsync($"{_baseUrl}/dashboards/address/{address}").Result;

                var jo = JObject.Parse(response.Content.ReadAsStringAsync().Result);

                var utxos = jo["data"][address]["utxo"].ToString();

                return JsonConvert.DeserializeObject<List<utxo>>(utxos);
            }
        }

        public class utxo
        {
            public uint block_id { get; set; }
            public string address { get; set; }
            public string transaction_hash { get; set; }
            public uint index { get; set; }
            public uint value { get; set; }
        }
    }
}

Part 7 - Bonus! - Live Price Data

As a parting gift, here's a simple utility class to fetch the current value (in USD) of Bitcoin Cash from the CoinGecko API:

public class CoinGeckoClient
{
    // https://www.coingecko.com/en/api/documentation
    private readonly string _baseUrl = "https://api.coingecko.com/api/v3";

    public decimal GetValue(string coinGeckoID = "bitcoin-cash")
    {
        var client = new HttpClient();

        var response = client.GetAsync($"{_baseUrl}/simple/price?ids={coinGeckoID}&vs_currencies=usd").Result;

        if (!response.IsSuccessStatusCode)
            return 0;

        var json = response.Content.ReadAsStringAsync().Result;

        dynamic data = JsonConvert.DeserializeObject(json);

        return data[coinGeckoID]["usd"];
    }
}

Conclusion

Thanks for reading! I hope you found this helpful. Let me know if you find any errors. Also, please leave any questions below and I'll do my best to answer them.

8
$ 7.07
$ 2.00 from @tula_s
$ 1.68 from @Marser
$ 1.10 from @Omar
+ 4
Avatar for thanah85
2 years ago

Comments