Intro to Ravencoin Development – Part 4 – Exchanging Assets

All code described below has been checked into github. Feel free to clone and go along with it as you read.

Welcome to our next journey into ravencoin development. This will be the last one in this intro series, and next i’ll focus on some other cool ravencoin projects including expanding out the library I’ve been building as part of this.

Anyways, I was running out of ideas, so I went to twitter:

So this is what we’ll do. Lets break down the logic of the application:

  1. A transaction comes in
  2. We need to identify what kind of transaction it is (Incoming/Outgoing? RVN? Asset? Fee?)
  3. We then look up if we’re willing to exchange our asset for the one that came in
    1. Or alternatively, have our application set to accept any asset for the asset we’re going to send
  4. Run through our existing logic and send the asset back to the origin address, like we did when we received RVN in the last post.

Just a quick note about what’s changed since the last post

I’ve been focusing a lot on the Ravencoin.ApplicationCore library, and much too much has changed there to post all the code. But the gist of it is:

  • All the classes that don’t say “Custom” are standard node commands. All the logic for we’ve done in the past are now part of “CustomX” classes, where X could be Transactions, Assets, etc. Anything in the Custom classes call the standard classes for data.
  • Logging improvements. You can inject log4net into this library from a web or console application and get full logging through the lifecycle.
  • Documentation. Mostly from the raven core node is now inside the library, so you can hover on a method and get info instead of googling it.
  • Almost fully complete. Most of the out of the box methods are implemented.

OK Lets get into it.

As I said above, its best to download the latest from github for this lesson since the class structure has changed. If you want to follow along with the tutorial, just copy everything over except CustomAssets and CustomTransactions and you should be ok.

We need to identify what kind of transaction it is (Incoming/Outgoing? RVN? Asset? Fee?)

I decided to use enums for this. Create a new Class inside Ravencoin.ApplicationCore/Models. Call it TransactionType.cs

namespace Ravencoin.ApplicationCore.Models
{
    public enum TType
    {
        RVN,
        Asset,
        Fee
    }
    public enum CType
    {
        Receive,
        Send
    }
    public class TransactionType
    {
        public TType transactionTypes { get; set; }

        public CType categoryTypes { get; set; }

    }
}

Now we’re going to use this class to write a method in CustomTransactions.cs to determine if the incoming transaction’s type:

public async Task<TransactionType> GetTransactionType(string txid, ServerConnection serverConnection) {
            Transactions transactions = new Transactions();
            //Get our in-wallet transaction data. Unlike GetPublicTransaction, this will give us details like category, amount, confirmations etc.
            ServerResponse response = await wallet.GetTransaction(serverConnection, txid);
            JObject result = JObject.Parse(response.responseContent);
            JToken resultDetails = result["result"]["details"];
            //Get the data we need to ensure this transaction is something we want to send an asset to 
            //Check to see is this is a payment in RVN or a fee payment or asset transfer transaction
            //Check to see if there are asset details in the transaction to determine if it's an asset transaction
            JToken assetDetails = result["result"]["asset_details"];
            JToken feeDetails = result["result"]["fee"];
            //If it has values in asset_details, it's an asset transaction.
            if (assetDetails.HasValues && feeDetails == null) {
                if (assetDetails[0]["category"].ToString() == "receive") {
                    return new TransactionType { transactionTypes = TType.Asset, categoryTypes = CType.Receive };
                } else { return new TransactionType { transactionTypes = TType.Asset, categoryTypes = CType.Receive }; }
            }
            //If it doesn't have asset_details, but still has result details, its a RVN Transaction
            else if (resultDetails.HasValues) {
                if (resultDetails[0]["category"].ToString() == "receive") { return new TransactionType { transactionTypes = TType.RVN, categoryTypes = CType.Receive }; } else { return new TransactionType { transactionTypes = TType.RVN, categoryTypes = CType.Receive }; }
            }
        // If it doesn't have details at all, it's a fee.
        else {
                return new TransactionType { transactionTypes = TType.Fee };
            }
        }

Over in CustomAssets.cs, we’re going to add a new method underneath ExchangeRvnForAsset. We’ll call this ExchangeAssetForAsset.

using log4net;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json.Linq;
using Ravencoin.ApplicationCore.Models;
using System;
using System.Net;
using System.Threading.Tasks;

namespace Ravencoin.ApplicationCore.BusinessLogic
{
    public class CustomAssets
    {
        //Inject Logger
        private static readonly ILog _logger = LogManager.GetLogger(typeof(CustomAssets));
        
        //Create a ravencore instance to call against
        private readonly Transactions transactions = new Transactions();
        private readonly Utilities utilities = new Utilities();
        private readonly CustomTransactions customtransactions = new CustomTransactions();
        private readonly Wallet wallet = new Wallet();
      
      public async Task<ServerResponse> ExchangeAssetForAsset(string txid, string assetReceiveAddress, string expectedIncomingAsset, string assetToSend, int? multiplier, int minConfirmations, ServerConnection serverConnection)
        {
            _logger?.Info($"TransactionID {txid}: Beginning ExchangeRvnForAsset Function. Transaction ID: {txid}, Asset Receive Address: {assetReceiveAddress}, Asset Received: {expectedIncomingAsset}, Asset To Send: {assetToSend}, Multiplier: {multiplier}, Minimum Confirmations: {minConfirmations}");
            int quantity = new int();
            Transactions transactions = new Transactions();
            Utilities utilities = new Utilities();

            //Get our in-wallet transaction data. Unlike GetPublicTransaction, this will give us details like category, amount, confirmations etc.
            ServerResponse response = await wallet.GetTransaction(serverConnection, txid);
            JObject result = JObject.Parse(response.responseContent);

            if (result != null)
            {
                //Get the data we need to ensure this transaction is something we want to send an asset to 
                //Check to see is this is a payment in RVN or a fee payment or asset transfer transaction
                JToken detailsArray = result["result"]["asset_details"];
                if (detailsArray.HasValues)
                {
                    string category = result["result"]["asset_details"][0]["category"].ToString();
                    string receiveAddress = result["result"]["asset_details"][0]["destination"].ToString();
                    double amountReceived = Double.Parse(result["result"]["asset_details"][0]["amount"].ToString());
                    string txAssetReceived = result["result"]["asset_details"][0]["asset_name"].ToString();
                    int confirmations = Int32.Parse(result["result"]["confirmations"].ToString());

                    //Round down the amount received. Rounding down discourages sending fractional RVN :)
                    int finalAmountReceived = Convert.ToInt32(Math.Floor(amountReceived));

                    //Check if we have a multiplier, and if its higher than one. If the multiplier is set to 100 for example, every 1 RVN Received will get 100 assets in response.
                    if (multiplier > 1 || multiplier != null) { quantity = (int)(finalAmountReceived * multiplier); }

                    //Check to see if assetReceived is set to * (wildcard). If it is we automatically set the assetMatch to true
                    bool assetMatch = false;
                    if (expectedIncomingAsset == "*") {
                        assetMatch = true;
                    } else if (expectedIncomingAsset == txAssetReceived) {
                        assetMatch = true;
                    };


                    //Check to see if we have enough of this asset to send.
                    Assets assets = new Assets();
                    ServerResponse assetBalanceRequest = await assets.ListMyAssets(serverConnection, assetToSend);
                    JObject assetBalanceResponse = JObject.Parse(assetBalanceRequest.responseContent);
                    int assetBalance = Int32.Parse(assetBalanceResponse["result"][assetToSend].ToString());

                    if (assetBalance >= quantity)
                    {
                        // Requirements:
                        // Category must be "receive" aka an icoming transaction
                        // Our expecteded receive address must match the one we're watching for
                        // The total amount of Assets is 1 or more.
                        // Confirmations must be equal or greater to the minimum confirmations we want. 
                        if (category == "receive" && receiveAddress == assetReceiveAddress && finalAmountReceived >= 1 && confirmations >= minConfirmations && assetMatch == true)
                        {
                            //Do a GetPublicTransaction on the txid to grab the vout address
                            ServerResponse getSenderAddress = await customtransactions.GetSenderAddress(serverConnection, txid);

                            if (getSenderAddress != null)
                            {
                                //Validate it's a valid Ravencoin Address  
                                ServerResponse checkIfValid = await utilities.ValidateAddress(getSenderAddress.responseContent, serverConnection);
                                JObject checkIfValidResponse = JObject.Parse(checkIfValid.responseContent);
                                bool isValid = bool.Parse(checkIfValidResponse["result"]["isvalid"].ToString());

                                if (isValid == true)
                                {
                                    ServerResponse sendAsset = await assets.Transfer(serverConnection, assetToSend, quantity, getSenderAddress.responseContent);
                                    sendAsset.errorEx = confirmations.ToString();
                                    return sendAsset;
                                }
                                else
                                {
                                    return new ServerResponse { statusCode = HttpStatusCode.InternalServerError, errorEx = "Invalid sender address" };
                                }
                            }
                            else
                            {
                                return new ServerResponse { statusCode = HttpStatusCode.InternalServerError, errorEx = "Could not find Sender Address" };
                            }
                        }
                        else
                        {
                            return new ServerResponse { statusCode = HttpStatusCode.InternalServerError, errorEx = $"The transaction didn't meet the minimum requirements. Incoming Address: {receiveAddress}, Amount: {finalAmountReceived}, Category: {category}, Confirmationss: { confirmations }" };
                        }
                    }
                    else
                    {
                        return new ServerResponse { statusCode = HttpStatusCode.InternalServerError, errorEx = $"Not enough Asset: {assetToSend} left in wallet." };
                    }
                }
                else
                {
                    return new ServerResponse { statusCode = HttpStatusCode.InternalServerError, errorEx = $"Transaction {txid} has no details section. Likely a fee transaction or an asset transfer transaction." };
                }
            }
            else
            {
                return new ServerResponse { statusCode = HttpStatusCode.InternalServerError, errorEx = $" Could not look up Transaction {txid}" };
            }
        }

        
    }
}

Notice on line 54 we’re checking for a * on the expected incoming asset. This is if we want to accept any asset in exchange for our asset, unless specifically configured in the next part.

That’s it for the class library. Lets move over to the Ravencoin.AssetSender console application.

Since the Ravencore wallet is going to run this application every time a transaction comes in, we need to write some logic right in the front here to determine if we’re going to accept RVN and send the asset, or accept and asset and send the asset.

using log4net;
using log4net.Config;
using Ravencoin.ApplicationCore.BusinessLogic;
using Ravencoin.ApplicationCore.Models;
using System;
using System.Threading.Tasks;
using System.Configuration;

namespace Ravencoin.AssetSender
{
   class Program
    {
        private static readonly ILog log = LogManager.GetLogger(typeof(Program));
        static async Task Main(string[] args)
        {
            XmlConfigurator.Configure();

            ServerConnection serverConnection = new ServerConnection{
                host = ConfigurationManager.AppSettings["host"],
                port = Int32.Parse(ConfigurationManager.AppSettings["port"]),
                username = ConfigurationManager.AppSettings["username"],
                password = ConfigurationManager.AppSettings["password"]
        };

            //Pull values from the config file
            string rvnListenAddress = ConfigurationManager.AppSettings["rvnListenAddress"];
            string assetListenAddress = ConfigurationManager.AppSettings["assetListenAddress"];
            string assetToSend = ConfigurationManager.AppSettings["assetToSend"];
            int multiplier = Int32.Parse(ConfigurationManager.AppSettings["multiplier"]);
            string expectedIncomingAsset = ConfigurationManager.AppSettings["expectedIncomingAsset"];
            int assetMultiplier = Int32.Parse(ConfigurationManager.AppSettings["assetMultiplier"]);

            try {
                //Get the txid from the args that the ravencoin node will supply when a wallet transaction comes in.
                if (args.Length > 0){
                    string txid = args[0].ToString();
                    log.Info($"Incoming transaction detected from {txid}");

                    //Look up & Log the transaction Type
                    Assets assets = new Assets();
                    CustomAssets cassets = new CustomAssets();
                    CustomTransactions ctransactions = new CustomTransactions();

                    TransactionType ttype = await ctransactions.GetTransactionType(txid, serverConnection);
                    log.Info($"TXID: {txid}. Transaction Type: {ttype.transactionTypes.ToString()}, Category: {ttype.categoryTypes.ToString()}");

                    //Deliver assets depending on the transaction type.
                    if (ttype.transactionTypes == TType.RVN && ttype.categoryTypes == CType.Receive) {
                        log.Info($"TXID: {txid}. Beginning ExchangeRvnForAsset method.");
                        //Send the details to the ExchangeRvnForAsset function in ApplicationCore.
                        ServerResponse response = await cassets.ExchangeRvnForAsset(txid, rvnListenAddress, assetToSend, multiplier, 1, serverConnection);

                        //Check if everything worked, and log the transaction ID of the Asset send. Otherwise, log the error.
                        if (response.statusCode == System.Net.HttpStatusCode.OK) {
                            log.Info($"Asset delivered successfully. Resulting transaction id = {response.responseContent}");
                            log.Info($"Confirmations: {response.errorEx}");
                        } else {
                            log.Error($"Error: {response.errorEx}");
                        }
                    } else if (ttype.transactionTypes == TType.Asset && ttype.categoryTypes == CType.Receive) {
                        log.Info($"TXID: {txid}. Beginning ExchangeAssetForAsset method.");
                        //Send the details to the ExchangeAssetForAsset function in ApplicationCore
                        ServerResponse response = await cassets.ExchangeAssetForAsset(txid, assetListenAddress, expectedIncomingAsset, assetToSend, assetMultiplier, 1, serverConnection);

                        //Check if everything worked, and log the transaction ID of the Asset send. Otherwise, log the error.
                        if (response.statusCode == System.Net.HttpStatusCode.OK) {
                            log.Info($"Asset delivered successfully. Resulting transaction id = {response.responseContent}");
                        } else {
                            log.Error($"Error: {response.errorEx}");
                        }
                    }    
                }
                else{
                    log.Error("No transaction ID passed in arguments");
                }
            }
            catch (Exception ex){
                log.Error(ex.Message);
            }
        }
    }
}

You can see on line 48 we’re taking action if the incoming transaction is RVN, and on line 60 if the incoming transaction is an asset.

I also wanted to make sure to have separate configuration options for exchanging assets instead of RVN. For example, I set my multiplier to 1 instead of 100 like we had when we were receiving RVN. Why? Because I had the expected asset received set to a wildcard (any asset, including the BULLSH token I own). So if i set the multiplier to 100, someone could send back my own assets on a loop until they get all my assets.

If you want to receive a specific asset, TRASH for example – you can just replace the * in expectedIncomingAsset to TRASH, and it will only take action if that asset is received. That said, any assets that arent TRASH will just sit in your wallet and the person who sent it will get nothing.

See the app.config below. You could technically expand on this and load many asset pairs into a database and take specific actions on each instead of hard coding it into a config like I have.

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
	<configSections>
		<section name="log4net" type="log4net.Config.Log4NetConfigurationSectionHandler, log4net" />
	</configSections>
	<log4net>
		<!-- In log4net, output destinations are known as appenders Roll the file when it reaches 1MB -->
		<appender name="LogFileAppender" type="log4net.Appender.RollingFileAppender">
			<!-- Specify which file to write to -->
			<param name="File" value="RavenCoin_AssetSender.log"/>
			<lockingModel type="log4net.Appender.FileAppender+MinimalLock" />
			<appendToFile value="true" />
			<rollingStyle value="Size" />
			<!-- How many log files should we keep? -->
			<maxSizeRollBackups value="2" />
			<!-- Roll to a new file when current one hits 1MB -->
			<maximumFileSize value="1MB" />
			<staticLogFileName value="true" />
			<!-- The format of each line in the log -->
			<layout type="log4net.Layout.PatternLayout">
				<param name="ConversionPattern" value="%d [%t] %-5p %c %m%n"/>
			</layout>
		</appender>
		<!-- Set root logger level to INFO and appender to LogFileAppender -->
		<root>
			<level value="INFO" />
			<appender-ref ref="LogFileAppender" />
		</root>
	</log4net>
	<appSettings>
		<add key ="host" value="127.0.0.1"/>
		<add key ="port" value="8766"/>
		<add key ="username" value="cryptobullsh"/>
		<add key ="password" value="testestest111111111"/>
		<add key="rvnListenAddress" value ="yourRavencoinIncomingAddress"/>
		<add key ="assetToSend" value="BULLSH"/>
		<add key ="multiplier" value="100"/>
		<add key ="assetListenAddress" value="yourRavencoinIncomingAddress"/>
		<add key ="expectedIncomingAsset" value="*"/>
		<add key ="assetMultiplier" value="1"/>
	</appSettings>
</configuration>

Now we’re ready to publish our application. Follow the steps in the previous blog post on publishing, and you should be set!

Here it is working:

2021-09-06 19:45:40,329 [1] INFO  Ravencoin.AssetSender.Program Incoming transaction detected from 06dca213ec0b49fd2b396f273c3505bfba3053086cd01e09cab8522722d36237
2021-09-06 19:45:40,615 [4] INFO  Ravencoin.AssetSender.Program TXID: 06dca213ec0b49fd2b396f273c3505bfba3053086cd01e09cab8522722d36237. Transaction Type: Asset, Category: Receive
2021-09-06 19:45:40,616 [4] INFO  Ravencoin.AssetSender.Program TXID: 06dca213ec0b49fd2b396f273c3505bfba3053086cd01e09cab8522722d36237. Beginning ExchangeAssetForAsset method.
2021-09-06 19:45:41,110 [5] INFO  Ravencoin.AssetSender.Program Asset delivered successfully. Resulting transaction id = {"result":["ad8cf09dea1bbf9f355a06f17a52bc8fe0881ba3955ce57d1540d2eeb1f49271"],"error":null,"id":"0"}

Hope this helps someone out there! Thanks as always for reading this far. Feel free to contact me on Twitter with questions.

Did I help? If you’re feeling generous, buy me a coffee by sending RVN to RHEH92NguBjaxXQPsM1bedkqqTXKr9EZcM

Intro to Ravencoin Development – Part 3 – Auto Asset Sender

All code described below has been checked into github. Feel free to clone and go along with it as you read.

As some of you saw on Twitter, I asked folks to beta test sending RVN to a certain address, and for every 1 RVN received they would automatically get back 100 “BULLSH” Assets.

I want to start by saying that while this is a cool little application, it does suffer some fundamental problems:

  • It involves trust of one party, this is not a smart contract – i.e. it does not exist on-chain. The user already sent you the RVN. Meaning:
    • What if I don’t hold up my end of the bargain?
    • What if there’s a bug?
    • What if I decide I don’t want to do this anymore and shut it down?
    • What if you run out of the asset your sending?
  • Because we don’t know the sender address, we have to look it up by matching the incoming transaction’s vin with the previous transaction’s vout address
    • This is not always guaranteed to work, though so far it’s worked flawlessly, at least with the official Ravencoin wallet/mobile wallet
    • Non-custodial wallets such as ones on exchanges don’t always send a transaction from the same address that would come back to the users account, so if exchanges ever support assets they may be lost in the void

If anything, this application functions more like an exchange – where you trust a company like Binance with your USD in exchange for whatever crypto you want to buy.

That said, once the sender executes the send to your address its on you to follow through – there’s no refund mechanism (though it would be pretty easy to write one upon any exception).

Also, i’m going to publish this code in a bit of an un-optimized state. I did this one a bit quick and dirty too, so I guess every other blog post will be a refactoring one πŸ˜‰

You may also have noticed I’m not tracking anything in a database. I purposely did not want to use a database because it makes explaining how things work a bit more difficult. There’s really no reason for it for this application outside of tracking what we sent, which is already in our logs. Since there could be multiple payments from the same address that we SHOULD send assets back multiple times, there’s no need to persist/validate that kind of transaction data. Maybe we will in the future.


OK Enough of the warnings, Let’s get into it.

Let’s go over the logic workflow of what we’re trying to accomplish:

  • Receive a transaction of RVN
  • Check to see if it’s a receive transaction, not an asset or a fee transaction
  • Look up that transaction and get the following info:
    • The address it came into
    • What category of transaction type it is
    • How much RVN it received
    • How many confirmations it has
  • Round down the amount it received to be a whole number (I rounded down to discourage fractional payments)
  • Apply a multiplier (i.e. for 1 RVN you get 100 assets)
  • Check to see if we have enough of the assets we’re sending in our wallet
  • Check to see if it was a “receive” type transaction, the address it came in on was the one we’re monitoring, and the amount of RVN is equal or greater than 1, and if it has the minimum amount of confirmations we want.
  • Look up the senders address to send the assets back to
  • Confirm it a valid Ravencoin address
  • If all that checks out, finally send the assets back to the address.

In the last lesson, in our BusinessLogic folder, we have Assets.cs. We’re going to add a few functions here underneath GetAssetData where we left off. If you need help, refer to the code I posted on github.

We’re going to add 3 functions.

  • TransferAsset
  • ListMyAssets
  • ExchangeRvnForAsset
 /// <summary>
        /// Transfers an Asset from your wallet to another.
        /// </summary>
        /// <param name="assetName">asset_name (string, required)</param>
        /// <param name="quantity">quantity (int, required)</param>
        /// <param name="toAddress">toAddress (string, required)</param>
        /// <param name="connection">ServerConnection (required)</param>
        /// <returns>
        /// Result:
        /// txid[txid]
        /// </returns>
        public static async Task<ServerResponse> TransferAsset(string assetName, int quantity, string toAddress, ServerConnection connection)
        {
            //Wrap properties in a JObject
            JObject commandParams = new JObject{
                { "asset_name", assetName },
                {"qty", quantity },
                {"to_address", toAddress }
            };

            //Set up the ServerCommand
            ServerCommand request = new ServerCommand()
            {
                commandId = "0",
                commandMethod = "transfer",
                commandParams = commandParams,
                commandJsonRpc = "2.0"
            };

            //Send the ServerCommand to RavenCore. See comments for Response Value
            ServerResponse response = await RpcConnections.RavenCore.Connect(request, connection);

            return response;
        }

        /// <summary>
        /// Looks up a specific asset from your wallet. This is an in-wallet only transaction.
        /// </summary>
        /// <param name="assetName"></param>
        /// <param name="connection"></param>
        /// <returns>Count of assets in your wallet.</returns>
        public static async Task<ServerResponse> ListMyAssets(string assetName, ServerConnection connection)
        {
            //Wrap properties in a JObject
            JObject commandParams = new JObject{
                { "asset", assetName }
            };

            //Set up the ServerCommand
            ServerCommand request = new ServerCommand()
            {
                commandId = "0",
                commandMethod = "listmyassets",
                commandParams = commandParams,
                commandJsonRpc = "2.0"
            };

            //Send the ServerCommand to RavenCore. See comments for Response Value
            ServerResponse response = await RpcConnections.RavenCore.Connect(request, connection);

            return response;
        }
        public static async Task<ServerResponse> ExchangeRvnForAsset(string txid, string rvnReceiveAddress, string asset, int? multiplier, int minConfirmations, ServerConnection serverConnection)
        {
            int quantity = new int();

            //Get our in-wallet transaction data. Unlike GetPublicTransaction, this will give us details like category, amount, confirmations etc.
            ServerResponse response = await Transactions.GetTransaction(txid, serverConnection);
            JObject result = JObject.Parse(response.responseContent);

            if (result != null)
            {
                //Get the data we need to ensure this transaction is something we want to send an asset to 
                //Check to see is this is a payment in RVN or a fee payment or asset transfer transaction
                JToken detailsArray = result["result"]["details"];
                if (detailsArray.HasValues) {
                    string category = result["result"]["details"][0]["category"].ToString();
                    string receiveAddress = result["result"]["details"][0]["address"].ToString();
                    double amountReceived = Double.Parse(result["result"]["amount"].ToString());
                    int confirmations = Int32.Parse(result["result"]["confirmations"].ToString());

                    //Round down the amount received. Rounding down discourages sending fractional RVN :)
                    int finalAmountReceived = Convert.ToInt32(Math.Floor(amountReceived));

                    //Check if we have a multiplier, and if its higher than one. If the multiplier is set to 100 for example, every 1 RVN Received will get 100 assets in response.
                    if (multiplier > 1 || multiplier != null) { quantity = (int)(finalAmountReceived * multiplier); }

                    //Check to see if we have enough of this asset to send.
                    ServerResponse assetBalanceRequest = await Assets.ListMyAssets(asset, serverConnection);
                    JObject assetBalanceResponse = JObject.Parse(assetBalanceRequest.responseContent);
                    int assetBalance = Int32.Parse(assetBalanceResponse["result"][asset].ToString());

                    if (assetBalance >= quantity)
                    {
                        // Requirements:
                        // Category must be "receive" aka an icoming transaction
                        // Our expecteded receive address must match the one we're watching for
                        // The total amount of RVN is 1 or more.
                        // Confirmations must be equal or greater to the minimum confirmations we want. 
                        if (category == "receive" && receiveAddress == rvnReceiveAddress && finalAmountReceived >= 1 && confirmations >= minConfirmations)
                        {
                            //Do a GetPublicTransaction on the txid to grab the vout address
                            ServerResponse getSenderAddress = await Transactions.GetSenderAddress(txid, serverConnection);

                            if (getSenderAddress != null)
                            {
                                //Validate it's a valid Ravencoin Address  
                                ServerResponse checkIfValid = await Utilities.ValidateAddress(getSenderAddress.responseContent, serverConnection);
                                JObject checkIfValidResponse = JObject.Parse(checkIfValid.responseContent);
                                bool isValid = bool.Parse(checkIfValidResponse["result"]["isvalid"].ToString());

                                if (isValid == true)
                                {
                                    ServerResponse sendAsset = await Assets.TransferAsset(asset, quantity, getSenderAddress.responseContent, serverConnection);
                                    sendAsset.errorEx = confirmations.ToString();
                                    return sendAsset;
                                }
                                else
                                {
                                    return new ServerResponse { statusCode = HttpStatusCode.InternalServerError, errorEx = "Invalid sender address" };
                                }
                            }
                            else
                            {
                                return new ServerResponse { statusCode = HttpStatusCode.InternalServerError, errorEx = "Could not find Sender Address" };
                            }
                        } else
                        {
                            return new ServerResponse { statusCode = HttpStatusCode.InternalServerError, errorEx = $"The transaction didn't meet the minimum requirements. Incoming Address: {rvnReceiveAddress}, Amount: {finalAmountReceived}, Category: {category}, Confirmationss: { confirmations }" };
                        }
                    }
                    else
                    {
                        return new ServerResponse { statusCode = HttpStatusCode.InternalServerError, errorEx = $"Not enough Asset: {asset} left in wallet." };
                    }
                }
                else
                {
                    return new ServerResponse { statusCode = HttpStatusCode.InternalServerError, errorEx = $"Transaction {txid} has no details section. Likely a fee transaction or an asset transfer transaction." };
                }
            } else
            {
                return new ServerResponse { statusCode = HttpStatusCode.InternalServerError, errorEx = $" Could not look up Transaction {txid}" };
            }
        }

TransferAsset simply executes the transfer to the RPC Server once the conditions are met.

ListMyAssets is a function that looks up how many of said asset you have left to send.

ExchangeRvnForAsset is the logic function to ensure we’re ok to send the asset and calls the other functions. There are other functions this calls, but in different Classes, since we organize them by type. We’ll get into that now.

Adding logic to Transactions.cs

In the last lesson, in our BusinessLogic folder, we have Transactions.cs. We’re going to add a few functions here underneath GetPublicTransaction where we left off. If you need help, refer to the code I posted on github.

We’re going to add 4 functions.

  • GetTransaction
  • GetTxOut (not really used, but we’ll keep it for later examples)
  • GetTransactionConfirmations
  • GetSenderAddress
/// <summary>
        /// Gets the transaction details for an in-wallet transaction ID. This will only return data on your own wallets transactions, not external ones. Use GetPublicTransaction for out of wallet transactions
        /// </summary>
        /// <param name="hexstring"></param>
        /// <param name="connection"></param>
        /// <returns>
        /// {
        ///  "amount" : x.xxx,        (numeric) The transaction amount in RVN
        ///  "fee": x.xxx,            (numeric) The amount of the fee in RVN.This is negative and only available for the 
        ///                              'send' category of transactions.
        ///  "confirmations" : n,     (numeric) The number of confirmations
        ///  "blockhash" : "hash",  (string) The block hash
        ///  "blockindex" : xx,       (numeric) The index of the transaction in the block that includes it
        ///  "blocktime" : ttt,       (numeric) The time in seconds since epoch(1 Jan 1970 GMT)
        ///  "txid" : "transactionid",   (string) The transaction id.
        ///  "time" : ttt, (numeric) The transaction time in seconds since epoch (1 Jan 1970 GMT)
        ///  "timereceived" : ttt,    (numeric) The time received in seconds since epoch(1 Jan 1970 GMT)
        ///  "bip125-replaceable": "yes|no|unknown",  (string) Whether this transaction could be replaced due to BIP125(replace-by-fee);
        ///        may be unknown for unconfirmed transactions not in the mempool
        ///  "details" : [
        ///    {
        ///      "account" : "accountname",      (string) DEPRECATED.The account name involved in the transaction, can be "" for the default account.
        ///      "address" : "address",          (string) The raven address involved in the transaction
        ///      "category" : "send|receive",    (string) The category, either 'send' or 'receive'
        ///      "amount" : x.xxx,                 (numeric) The amount in RVN
        ///      "label" : "label",              (string) A comment for the address/transaction, if any
        ///      "vout" : n,                       (numeric) the vout value
        ///      "fee": x.xxx,                     (numeric) The amount of the fee in RVN.This is negative and only available for the 
        ///                                           'send' category of transactions.
        ///      "abandoned": xxx(bool) 'true' if the transaction has been abandoned(inputs are respendable). Only available for the 
        ///                                           'send' category of transactions.
        ///    }
        ///    ,...
        ///  ],
        ///  "asset_details" : [
        ///    {
        ///      "asset_type" : "new_asset|transfer_asset|reissue_asset", (string) The type of asset transaction
        ///      "asset_name" : "asset_name",          (string) The name of the asset
        ///      "amount" : x.xxx,                 (numeric) The amount in RVN
        ///      "address" : "address",          (string) The raven address involved in the transaction
        ///      "vout" : n,                       (numeric) the vout value
        ///      "category" : "send|receive",    (string) The category, either 'send' or 'receive'
        ///    }
        ///    ,...
        ///  ],
        ///  "hex" : "data"(string) Raw data for transaction
        ///}
        /// </returns>
        public static async Task<ServerResponse> GetTransaction(string txid, ServerConnection connection)
        {
            //Set up parameters to get the hex string of the transaction
            JObject commandParams = new JObject(
                new JProperty("txid", txid)
            );
            //Set up the Ravcencoin Object
            ServerCommand request = new ServerCommand()
            {
                commandId = "0",
                commandMethod = "gettransaction",
                commandParams = commandParams,
                commandJsonRpc = "2.0"
            };

            //Get the hex string of the transaction back from getrawtransaction, and then parse it to get just the raw hex string from result
            ServerResponse response = await RpcConnections.RavenCore.Connect(request, connection);
            return response;
        }

        /// <summary>
        /// Returns details about an unspent transaction output.
        /// </summary>
        /// <param name="txid"></param>
        /// <param name="connection"></param>
        /// <returns>
        /// {
        ///  "bestblock" : "hash",    (string) the block hash
        ///  "confirmations" : n,       (numeric) The number of confirmations
        ///  "value" : x.xxx,           (numeric) The transaction value in RVN
        ///  "scriptPubKey" : {         (json object)
        ///     "asm" : "code",       (string) 
        ///     "hex" : "hex",        (string) 
        ///     "reqSigs" : n,          (numeric) Number of required signatures
        ///     "type" : "pubkeyhash", (string) The type, eg pubkeyhash
        ///     "addresses" : [          (array of string) array of raven addresses
        ///        "address"     (string) raven address
        ///        ,...
        ///     ]
        ///  },
        ///  "coinbase" : true|false   (boolean) Coinbase or not
        ///}
        /// </returns>
        public static async Task<ServerResponse> GetTxOut(string txid, ServerConnection connection)
        {
            //Set up parameters to get the hex string of the transaction
            JObject commandParams = new JObject(
                new JProperty("txid", txid),
                new JProperty("n", 0)
            );
            //Set up the Ravcencoin Object
            ServerCommand request = new ServerCommand()
            {
                commandId = "0",
                commandMethod = "gettxout",
                commandParams = commandParams,
                commandJsonRpc = "2.0"
            };

            //Get the hex string of the transaction back from getrawtransaction, and then parse it to get just the raw hex string from result
            ServerResponse response = await RpcConnections.RavenCore.Connect(request, connection);

            return response;
        }

        /// <summary>
        /// Looks up the Transaction info from TxOut and responsds with the number of confirmations.
        /// </summary>
        /// <param name="txid"></param>
        /// <param name="connection"></param>
        /// <returns>string representation of number of confirmations</returns>
        public static async Task<ServerResponse> GetTransactionConfirmations(string txid, ServerConnection connection)
        {
            //Get the RawTransaction and return the hexcode
            ServerResponse response = await GetTxOut(txid, connection);

            //Parse the result for the confirmations
            JObject result = JObject.Parse(response.responseContent);

            //put the confirmations back into the ServerResponse object
            response.responseContent = result["result"]["confirmations"].ToString();

            return response;
        }

        public static async Task<ServerResponse> GetSenderAddress(string txid, ServerConnection connection)
        {
            //Get the transaction info for the incoming txid
            ServerResponse firstTxRequest = await Transactions.GetPublicTransaction(txid, connection);
            JObject  firstTxResponse = JObject.Parse(firstTxRequest.responseContent);

            //The assumption is, if we look up the vin txid from the incoming transaction, and look at the first vout - this SHOULD be the owners wallet. 
            //USE AT YOUR OWN RISK, THIS IS NOT GUARANTEED
            string secondTxId = firstTxResponse["result"]["vin"][0]["txid"].ToString();
            //Grab the vout from the vin of the first transaction. We'll use this to match the previous transactions index of the vout.
            int voutToMatch = Int32.Parse(firstTxResponse["result"]["vin"][0]["vout"].ToString());

            ServerResponse secondTxRequest = await Transactions.GetPublicTransaction(secondTxId, connection);
            JObject secondTxResponse = JObject.Parse(secondTxRequest.responseContent);
            //Parse out for the first address in vout

            secondTxRequest.responseContent = secondTxResponse["result"]["vout"][voutToMatch]["scriptPubKey"]["addresses"][0].ToString();


            return secondTxRequest;
        }

GetTransaction gets the transaction details that are in-wallet only. It gives you much easier access to data about your own transactions like amount, confirmations, etc – but ONLY works on in-wallet transactions. Anything out of wallet needs to be looked up via GetPublicTransaction.

GetTxOut gets the details of a transactions unspent outputs. We’re not going to use this here, but like i said we’ll keep it in for further examples.

GetConfirmations uses GetTxOut to pull the confirmations from a transaction. We’re not really going to use this here either, since GetTransaction will give us this data easier. However, we’ll keep it in if we ever need to look up confirmations of a non in-wallet transaction.

GetSenderAddress looks up the sender address from the previous transaction. This function is a bit confusing, as we’re doing three things here:

  • We’re looking at the incoming transaction’s “vin” section and grabbing that transaction id and the integer (i.e. index) of the previous transaction’s “vout”
  • We look up the txid from the “vin” from the previous 1st bullet point
  • We match on the index of the “vout” section of that transaction using the index of the “vin” from the first transaction and return the first address in the array

I was not the first one to face this kind of problem, this is specific to bitcoin which Ravencoin is a fork of. I found the “answer” in a discussion on how to do this for a SatoshiDice style game of all places.

That SHOULD result in the senders address that sent you the RVN. Please heed my warnings about this in the intro of this post. It is NOT guaranteed.

Adding Utilities.cs

We’ve got a new type of call to make, and it’s to check if the Ravencoin address is valid. In the official node commands, its listed under “Utilities”, so we’ll do the same in our class library and make a new class for Utilities.

Right Click business logic, go to Add->Class and name it Utilities.cs and add this code:

using Ravencoin.ApplicationCore.Models;
using System.Threading.Tasks;
using Newtonsoft.Json.Linq;

namespace Ravencoin.ApplicationCore.BusinessLogic
{
    public class Utilities
    {
        public static async Task<ServerResponse> ValidateAddress(string address, ServerConnection connection)
        {
            //Wrap properties in a JObject
            JObject commandParams = new JObject();
            commandParams.Add("address", address);

            //Set up the ServerCommand
            ServerCommand request = new ServerCommand(){
                commandId = "0",
                commandMethod = "validateaddress",
                commandParams = commandParams,
                commandJsonRpc = "2.0"
            };

            //Send the ServerCommand to RavenCore. See comments for Response Value
            ServerResponse response = await RpcConnections.RavenCore.Connect(request, connection);

            return response;
        }
    }
}

At this point, we’re done with our ApplicationCore project. We have all the functions we need to move forward with the next step.


Creating a console application to drive the logic

You might be asking yourself right now, “cool, we wrote a bunch of functions, but how are we going to trigger it when we get RVN sent to us?” Remember in Part 2 how I said the reason we’re moving all this code into a class library is for reusability? Well, here is where that comes into play. So far we’ve only been calling this library from our web project. Now we’re going to make a console application that re-uses the same code we’ve been using for the web project.

If you’re now asking yourself, “why not use the web project?” Well, we’re going to make use of the “walletnotify” feature to trigger the code. It launches a script or executable and passes the transaction ID as an argument. It doesn’t make a ton of sense to write a script to call our web project to then execute this logic. Technically we could use powershell to call the classes directly, but I wanted to keep everything .net native in visual studio.

OK, right click the solution “RavencoinApiExample” at the top of solution explorer, and Add->new project. Choose C# Console Application.

In the next screen, name it “Ravencoin.AssetSender. Click next and then choose .NET Core 3.1.

You’ll see the new project and it’ll have one file – Program.cs.

We’re going to add some dependencies and configuration first.

Right click the project and click Manage NuGet Packages. Click browse and search for Log4net and install it. We’re going to use Log4net since its just easier in a console application IMO.

Now we’re going to add a dependency on our class library. Right click Dependencies in the project explorer and click “Add Project Reference”. Click the checkbox next to Ravencoin.ApplicationcCore and click OK.

Now we need a config file, right click on the project Ravencoin.AssetSender and go to Add->New Item. Find the “Application Configuration File”. The default name is App.config. Add this.

Open the App.config file and add this:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
	<configSections>
		<section name="log4net" type="log4net.Config.Log4NetConfigurationSectionHandler, log4net" />
	</configSections>
	<log4net>
		<!-- In log4net, output destinations are known as appenders Roll the file when it reaches 1MB -->
		<appender name="LogFileAppender" type="log4net.Appender.RollingFileAppender">
			<!-- Specify which file to write to -->
			<param name="File" value="RavenCoin_AssetSender.log"/>
			<lockingModel type="log4net.Appender.FileAppender+MinimalLock" />
			<appendToFile value="true" />
			<rollingStyle value="Size" />
			<!-- How many log files should we keep? -->
			<maxSizeRollBackups value="2" />
			<!-- Roll to a new file when current one hits 1MB -->
			<maximumFileSize value="1MB" />
			<staticLogFileName value="true" />
			<!-- The format of each line in the log -->
			<layout type="log4net.Layout.PatternLayout">
				<param name="ConversionPattern" value="%d [%t] %-5p %c %m%n"/>
			</layout>
		</appender>
		<!-- Set root logger level to INFO and appender to LogFileAppender -->
		<root>
			<level value="INFO" />
			<appender-ref ref="LogFileAppender" />
		</root>
	</log4net>
	<appSettings>
		<add key ="host" value="127.0.0.1"/>
		<add key ="port" value="8766"/>
		<add key ="username" value="cryptobullsh"/>
		<add key ="password" value="test12311"/>
		<add key="rvnListenAddress" value ="yourRVNAddressInYourWallet"/>
		<add key ="assetToSend" value="BULLSH"/>
		<add key ="multiplier" value="100"/>
	</appSettings>
</configuration>

You’ll notice we have some logging settings and then some server settings as well as a few more things. We’re going to make an address in the RVN wallet later to configure the rvnListenAddress.

assetToSend is what you have in your wallet that you want this to send. You can send some BULLSH if you have some, or any other asset you might have – or you can mint your own main asset for 500RVN like I did with BULLSH.

multiplier is how many of the asset you want to send for every one RVN received. You may want to make it 1 to 1, you may want to make it 10. Whatever you want!

Back to Program.cs – Put this code in there:

using log4net;
using log4net.Config;
using Ravencoin.ApplicationCore.BusinessLogic;
using Ravencoin.ApplicationCore.Models;
using System;
using System.Threading.Tasks;
using System.Configuration;
namespace Ravencoin.AssetSender
{
    class Program
    {
        private static readonly ILog log = LogManager.GetLogger(typeof(Program));
        static async Task Main(string[] args)
        {
            XmlConfigurator.Configure();

            ServerConnection serverConnection = new ServerConnection{
                host = ConfigurationManager.AppSettings["host"],
                port = Int32.Parse(ConfigurationManager.AppSettings["port"]),
                username = ConfigurationManager.AppSettings["username"],
                password = ConfigurationManager.AppSettings["password"]
        };

            //Pull values from the config file
            string rvnListenAddress = ConfigurationManager.AppSettings["rvnListenAddress"];
            string assetToSend = ConfigurationManager.AppSettings["assetToSend"];
            int multiplier = Int32.Parse(ConfigurationManager.AppSettings["multiplier"]);
            try {
                //Get the txid from the args that the ravencoin node will supply when a wallet transaction comes in.
                if (args.Length > 0){
                    string txid = args[0].ToString();
                    log.Info($"Incoming transaction detected from {txid}");
                    log.Info($"Beginning asset send for {txid}");

                    //Send the details to the ExchangeRvnForAsset function in ApplicationCore.
                    ServerResponse response = await Assets.ExchangeRvnForAsset(txid, rvnListenAddress, assetToSend, multiplier, 1, serverConnection);

                    //Check if everything worked, and log the transaction ID of the Asset send. Otherwise, log the error.
                    if (response.statusCode == System.Net.HttpStatusCode.OK){
                        log.Info($"Asset delivered successfully. Resulting transaction id = {response.responseContent}");
                        log.Info($"Confirmations: {response.errorEx}");
                    }
                    else{
                        log.Error($"Error: {response.errorEx}");
                    }
                }
                else{
                    log.Error("No transaction ID passed in arguments");
                }
            }
            catch (Exception ex){
                log.Error(ex.Message);
            }
        }
    }
}

The majority of this program is logging. Line 36 is really the only line of code that executes anything important – the rest is calling the BusinessLogic in our class library. Cool right?

Setting up your receiving address

We’re going to make a unique address for this to listen to. Why? Well for example, you might be mining Ravencoin, or buying some RVN from an exchange and sending it to this wallet. We wouldn’t want the address we use for that to automatically respond to a mining/purchase payment with assets that will essentially end up in the void, right?

Head over to your Ravencore wallet you’ve been using as your RPC server, and click “Receive”.

Type “AssetSender” in the “Label” textbox, just so we remember what it is. Leave “Amount” and “Message” blank, and do not check the “reuse address” option. Click “Request Payment”.

A new window will pop up with the QR code. Click the “Copy Address” button, and go paste that address into your App.config value for rvnListenAddress.

Publishing your application

We’re getting close to the finish line!

Back in visual studio where you have your “Ravencoin.AssetSender” project open, Click the “Build” menu and choose “Publish Ravencoin.AssetSender.

We’ll have to make a publishing profile. Choose “Folder” as the target.

Choose folder on the next screen again as your specific target

It will ask you for a folder location. I chose C:\RavencoinAssetSender.

You’re now ready to publish! Click the “Publish” Button:

Now head over to c:\RavencoinAssetSender

Notice how it built our class library into a DLL? the Ravencoin.AssetSender.exe will be referenceing all of our code specifically in that file.

Triggering the application from RavenCore

OK final step! We need to tell RavenCore what to call to trigger this. Open up your raven.conf. The easiest way is to go to your wallet and click “Wallet” ->”Options”, and then click “Open configuration file”.

We’re going to keep all the options we set previously, we’re just going to add this to the bottom:


walletnotify=C:\RavencoinAssetSender\Ravencoin.AssetSender.exe %s

The %s is the argument that passes the transaction id to the AssetSender.

You need to restart the wallet for this to take effect.

When you get a transaction, you’ll see a command window pop up when a transaction comes through a few times. This is normal.

MAKE SURE YOU HAVE YOUR rvnListenAddress DEFINED IN YOUR App.config!!

A note about confirmations, and watching your logs

Something i noticed early on in my testing was that this sent the assets immediately after RVN was received. Then about a minute later it sent double the assets for that same RVN transaction. Well here’s why:

walletnotify will call your application when the transaction is received, and again when it sees it for the first time in a block.

This is why I added a check to make sure there was at least one minimum confirmation, which makes the application skip the first incoming transaction and only REALLY sends the assets when it sees it as a block.

If you send yourself a transaction to your AssetSender wallet address, you should see exactly that in the logs:

Open C:\RavencoinAssetSender\RavenCoin_AssetSender.log (if it doesn’t exist, it will after a transaction goes through).

walletnotify will actually launch our application on ANY changes to your wallet, which is why we also added checks to see if it was a receive, check if its a fee transaction, etc. A single transaction will log something like this:

----- First transaction comes through ----
2021-08-18 19:50:26,004 [1] INFO  Ravencoin.AssetSender.Program Incoming transaction detected from <Transaction>
2021-08-18 19:50:26,019 [1] INFO  Ravencoin.AssetSender.Program Beginning asset send for <Transaction>
2021-08-18 19:50:26,577 [5] ERROR Ravencoin.AssetSender.Program Error: The transaction didn't meet the minimum requirements. Incoming Address: <myAddress>, Amount: 1, Category: receive, Confirmationss: 0
  
----- Block transaction comes through, It met the requirements, so its sending ----- 
2021-08-18 19:52:00,760 [1] INFO  Ravencoin.AssetSender.Program Incoming transaction detected from <Transaction>
2021-08-18 19:52:00,784 [1] INFO  Ravencoin.AssetSender.Program Beginning asset send for <Transaction>
2021-08-18 19:52:01,621 [5] INFO  Ravencoin.AssetSender.Program Asset delivered successfully. Resulting transaction id = {"result":["<NewTransactionNumber>"],"error":null,"id":"0"}
2021-08-18 19:52:01,622 [5] INFO  Ravencoin.AssetSender.Program Confirmations: 1

----- Doing Logic checks on the subsequent Asset transaction and Fee transaction -----
2021-08-18 19:52:02,307 [1] INFO  Ravencoin.AssetSender.Program Incoming transaction detected from <AssetTransaction>
2021-08-18 19:52:02,318 [1] INFO  Ravencoin.AssetSender.Program Beginning asset send for <AssetTransaction>
2021-08-18 19:52:02,324 [1] INFO  Ravencoin.AssetSender.Program Incoming transaction detected from <AssetTransaction>
2021-08-18 19:52:02,338 [1] INFO  Ravencoin.AssetSender.Program Beginning asset send for <AssetTransaction>
2021-08-18 19:52:02,681 [1] ERROR Ravencoin.AssetSender.Program Error: Transaction <AssetTransaction> has no details section. Likely a fee transaction or an asset transfer transaction.
2021-08-18 19:52:02,692 [1] ERROR Ravencoin.AssetSender.Program Error: Transaction <AssetTransaction> has no details section. Likely a fee transaction or an asset transfer transaction.
2021-08-18 19:56:02,344 [1] INFO  Ravencoin.AssetSender.Program Incoming transaction detected from <AssetTransaction>
2021-08-18 19:56:02,355 [1] INFO  Ravencoin.AssetSender.Program Beginning asset send for <AssetTransaction>
2021-08-18 19:56:02,682 [4] ERROR Ravencoin.AssetSender.Program Error: Transaction <AssetTransaction> has no details section. Likely a fee transaction or an asset transfer transaction.

Well, that was a lot. If you read this far, thanks for reading and I hope you got something from this!

Did I help? If you’re feeling generous, buy me a coffee by sending RVN to RHEH92NguBjaxXQPsM1bedkqqTXKr9EZcM

Intro to Ravencoin Development – Part 2 – Refactoring

All code described below has been checked into github. Feel free to clone and go along with it as you read.

I wanted to choose something more exciting than refactoring and cleaning up for part 2, but what we built in Part 1 is “quick and dirty”. Some of the design principles are simply not there if we want to continue to expand upon this project.

When I decided to refactor what we wrote in the last lesson, I wanted to get these features out of it:

  • Building an ApplicationCore class library specifically for business logic
    • This allows for code to be re-used by other applications
      • For example, last lesson we built a web api. What if we wanted to build a command line application or a winforms (ugh) application? We wouldn’t want to write the same code twice, that’s bad practice.
    • Enables us to make unit tests
  • Write fully async/awaited code
    • Allows us to run calls asynchronously. No blocking waiting for other tasks to complete
  • Create common command/response/connection classes to pass back and forth
    • Stored in the ApplicationCore class library for reusability
  • The client (web, console, etc) application should only be passing expected variables for the RPC function using native types
    • i.e. the web client should not have to worry about creating a json object to pass to the rpc server
  • Lots of comments
    • Including summary/param/returns comments, so Intellisense tells you what the function expects
  • Using dependency injection to pass our connection details
  • Logging
  • A bit more consistent naming (PascalCase for Functions, camelCase for Properties)

Creating a new Application

Using the existing project, right click “Solution ‘RavcencoinApiExample'” at the top of solution explorer and go to Add -> New Project

Choose Class library:

Click Next, then name it “Ravencoin.ApplicationCore”, Click next again, and make sure. .Net Core 3.1 is selected. Click Create.

Next, Rename the existing Web project to “Ravencoin.Web”. When this is complete, you should have two items in your project similar to this:

We’ll also need to add Newtonsoft.Json to this project. Right clock Ravencoin.ApplicationCore -> Manage NuGet Packages. Click Browse and search for Newtonsoft.Json and Install it.


Create Folders in Ravencoin.ApplicationCore

We’re going to organize things a bit better.

Right click Ravencoin.ApplicationCore and go to Add => New Folder.

Name the new folder “Models”

Do this again for two more folders, “BusinessLogic” and “RpcConnections”

It should look like this after you’re done:


Creating The RPC Connection class

Right click the RpcConnections folder, and go to Add->Class. Name it “Ravencore.cs”. This will be the class that handles the connection to the Ravencoin RPC node. This time we’re using asynchronous code, returning a Task<T> response, and doing some minor exception handling. Also notice we’re passing two objects:

  • ServerCommand object (similar to our previous RavencoinRequest class)
  • ServerConnection object (stores hostname/port/username/password)

And it will return a ServerResponse object.

using Newtonsoft.Json;
using System;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using Ravencoin.ApplicationCore.Models;
using System.Threading.Tasks;

namespace Ravencoin.ApplicationCore.RpcConnections
{
    class RavenCore
    {
        public static async Task<ServerResponse> Connect(ServerCommand command, ServerConnection connection)
        {
            HttpClient client = new HttpClient();
            Uri baseUri = new Uri($"http://{connection.host}:{connection.port}");
            client.BaseAddress = baseUri;
            client.DefaultRequestHeaders.Clear();
            client.DefaultRequestHeaders.ConnectionClose = true;
            client.DefaultRequestHeaders.Add("Accept", "application/json");

            //Set up authentication
            var authenticationString = $"{connection.username}:{connection.password}";
            var base64EncodedAuthenticationString = Convert.ToBase64String(System.Text.ASCIIEncoding.ASCII.GetBytes(authenticationString));

            //Set up the body of the message.
            //This adds the authorization header and encoding the RavencoinRequest object into json.
            var requestMessage = new HttpRequestMessage(HttpMethod.Post, "/");
            requestMessage.Headers.Authorization = new AuthenticationHeaderValue("Basic", base64EncodedAuthenticationString);

            //Set the content of the body
            requestMessage.Content = new StringContent(JsonConvert.SerializeObject(command), Encoding.UTF8, "application/json");

            // Try to send the message, if we get an error response as such with details
            try
            {
                //make the request
                HttpResponseMessage httpresponse = await client.SendAsync(requestMessage);

                //ensure we get a good response
                httpresponse.EnsureSuccessStatusCode();

                ServerResponse response = new ServerResponse {
                    statusCode = System.Net.HttpStatusCode.OK,
                    responseContent = await httpresponse.Content.ReadAsStringAsync()
                };

                return response;

            }
            catch (HttpRequestException httpEx)
            {
                ServerResponse exresponse = new ServerResponse
                {
                    statusCode = System.Net.HttpStatusCode.BadRequest,
                    errorEx = httpEx.Message
                };

                return exresponse;

            }
        }
    }
}

This will leave you with a bunch of errors, because those objects don’t exist yet.


Creating the Models

Right Click the Models folder and go to Add-> Class. Name it ServerResponse.cs. Do this again two more times for ServerCommand.cs, and ServerConnection.cs.

In ServerResponse.cs, put in:

using System.Net;
namespace Ravencoin.ApplicationCore.Models
{
    public class ServerResponse
    {
        public HttpStatusCode statusCode { get; set; }
        public string errorEx { get; set; }
        public string responseContent { get; set; }
    }
}

In ServerCommand.cs, put in:

using Newtonsoft.Json;
using Newtonsoft.Json.Linq;

namespace Ravencoin.ApplicationCore.Models
{
    public class ServerCommand
    {
        [JsonProperty(PropertyName = "id")]
        public string commandId { get; set; }

        [JsonProperty(PropertyName = "method")]
        public string commandMethod { get; set; }

        [JsonProperty(PropertyName = "params")]
        public JObject commandParams { get; set; }

        [JsonProperty(PropertyName = "jsonrpc")]
        public string commandJsonRpc { get; set; }
    }
}

In ServerConnection.cs, put in:

namespace Ravencoin.ApplicationCore.Models
{ 
    public class ServerConnection
    {
        public string host { get; set; }
        public int port { get; set; }
        public string username { get; set; }
        public string password { get; set; }
    }
}

Adding Business Logic

As I said above, we should consider this class library to be akin to a driver that our application uses. It does all the heavy lifting and formatting of what we want it to do.

Let’s start with our blockchain logic. Right click the BusinessLogic folder, Go to Add -> Class. Name it Blockchain.cs.

Add this code to Blockchain.cs:

using Ravencoin.ApplicationCore.Models;
using System.Threading.Tasks;

namespace Ravencoin.ApplicationCore.BusinessLogic
{
    public class Blockchain
    {
        /// <summary>
        /// Returns an object containing various state info regarding blockchain processing.
        /// </summary>
        /// <param name="connection">ServerConnection (required)</param>
        /// <returns>A Json response with the current state of the blockchain</returns>
        public static async Task<ServerResponse> GetBlockchainInfo(ServerConnection connection)
        {
            ServerCommand request = new ServerCommand()
            {
                commandId = "0",
                commandMethod = "getblockchaininfo",
                commandJsonRpc = "2.0"
            };

            ServerResponse response = await RpcConnections.RavenCore.Connect(request, connection);

            return response;
        }

        /// <summary>
        /// Returns the number of blocks in the longest blockchain.
        /// </summary>
        /// <param name="connection">ServerConnection (required)</param>
        /// <returns>
        /// Result:
        /// n(numeric) The current block count
        /// </returns>
        public static async Task<ServerResponse> GetBlockCount(ServerConnection connection)
        {
            ServerCommand request = new ServerCommand()
            {
                commandId = "0",
                commandMethod = "getblockcount",
                commandJsonRpc = "2.0"
            };

            ServerResponse response = await RpcConnections.RavenCore.Connect(request, connection);

            return response;
        }
    }
}
ο»Ώ

Notice there’s a lot more comments above the functions. if you type /// <enter> in visual studio above your functions, it will autogenerate this comment style. This also will be displayed by intellisense when we’re calling it later, similar to this:

Lets do our Assets logic. Right click the BusinessLogic folder, Go to Add -> Class. Name it Assets.cs.

Add this code to Assets.cs:

using Ravencoin.ApplicationCore.Models;
using System.Threading.Tasks;
using Newtonsoft.Json.Linq;

namespace Ravencoin.ApplicationCore.BusinessLogic
{
    public class Assets
    {
        /// <summary>
        /// Gets data about a particular asset. 
        /// </summary>
        /// <param name="assetName"> asset_name (string, required)</param>
        /// <param name="connection"> ServerConnection (required) </param>
        /// <returns>
        /// Result:
        ///{
        ///  name: (string),
        ///  amount: (number),
        ///  units: (number),
        ///  reissuable: (number),
        ///  has_ipfs: (number),
        ///  ipfs_hash: (hash), (only if has_ipfs = 1 and that data is a ipfs hash)
        ///  txid_hash: (hash), (only if has_ipfs = 1 and that data is a txid hash)
        ///  verifier_string: (string)
        /// }
        /// </returns>
        /// 
        public static async Task<ServerResponse> GetAssetData(string assetName, ServerConnection connection)
        {

            //Wrap properties in a JObject
            JObject commandParams = new JObject();
            commandParams.Add("asset_name", assetName);
            

            //Set up the ServerCommand
            ServerCommand request = new ServerCommand()
            {
                commandId = "0",
                commandMethod = "getassetdata",
                commandParams = commandParams,
                commandJsonRpc = "2.0"
            };

            //Send the ServerCommand to RavenCore. See comments for Response Value
            ServerResponse response = await RpcConnections.RavenCore.Connect(request, connection);

            return response;
        }
    }
}

Lets do our Transactions logic. Right click the BusinessLogic folder, Go to Add -> Class. Name it Transactions.cs.

Add this code to Transactions.cs:

using Newtonsoft.Json.Linq;
using Ravencoin.ApplicationCore.Models;
using System.Threading.Tasks;

namespace Ravencoin.ApplicationCore.BusinessLogic
{
     public class Transactions
    {
        /// <summary>
        /// 
        /// </summary>
        /// <param name="txid"></param>
        /// <param name="connection"></param>
        /// <returns>"data"      (string) The serialized, hex-encoded data for 'txid'</returns>
        public static async Task<ServerResponse> GetRawTransaction(string txid, ServerConnection connection)
        {
            //Set up parameters to get the hex string of the transaction
            JObject commandParams = new JObject(
                new JProperty("txid", txid)
            );
            //Set up the Ravcencoin Object
            ServerCommand request = new ServerCommand()
            {
                commandId = "0",
                commandMethod = "getrawtransaction",
                commandParams = commandParams,
                commandJsonRpc = "2.0"
            };

            //Get the hex string of the transaction back from getrawtransaction, and then parse it to get just the raw hex string from result
            ServerResponse response = await RpcConnections.RavenCore.Connect(request, connection);

            //Parse the result for the hexstring
            JObject result = JObject.Parse(response.responseContent);
            JToken hexstring = result["result"];

            response.responseContent = hexstring.ToString();

            return response;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="hexstring"></param>
        /// <param name="connection"></param>
        /// <returns>
        /// {
        ///  "txid" : "id",        (string) The transaction id
        ///  "hash" : "id",        (string) The transaction hash(differs from txid for witness transactions)
        ///  "size" : n,             (numeric) The transaction size
        ///  "vsize" : n,            (numeric) The virtual transaction size(differs from size for witness transactions)
        ///  "version" : n,          (numeric) The version
        ///  "locktime" : ttt,       (numeric) The lock time
        ///  "vin" : [               (array of json objects)
        ///     {
        ///       "txid": "id",    (string) The transaction id
        ///       "vout": n,         (numeric) The output number
        ///       "scriptSig": {     (json object) The script
        ///         "asm": "asm",  (string) asm
        ///         "hex": "hex"   (string) hex
        ///       },
        ///       "txinwitness": ["hex", ...] (array of string) hex-encoded witness data(if any)
        ///       "sequence": n(numeric) The script sequence number
        ///     }
        ///     ,...
        ///  ],
        ///  "vout" : [             (array of json objects)
        ///     {
        ///       "value" : x.xxx,            (numeric) The value in RVN
        ///       "n" : n,                    (numeric) index
        ///       "scriptPubKey" : {          (json object)
        ///         "asm" : "asm",          (string) the asm
        ///         "hex" : "hex",          (string) the hex
        ///         "reqSigs" : n,            (numeric) The required sigs
        ///         "type" : "pubkeyhash",  (string) The type, eg 'pubkeyhash'
        ///         "asset" : {               (json object) optional
        ///           "name" : "name",      (string) the asset name
        ///           "amount" : n,           (numeric) the amount of asset that was sent
        ///           "message" : "message", (string optional) the message if one was sent
        ///           "expire_time" : n,      (numeric optional) the message epoch expiration time if one was set
        ///         "addresses" : [           (json array of string)
        ///           "12tvKAXCxZjSmdNbao16dKXC8tRWfcF5oc"   (string) raven address
        ///           ,...
        ///         ]
        ///       }
        ///     }
        ///     ,...
        ///  ],
        ///}
        /// </returns>
        public static async Task<ServerResponse> DecodeRawTransaction(string hexstring, ServerConnection connection)
        {
            //Set up parameters to get the hex string of the transaction
            JObject commandParams = new JObject(
                new JProperty("hexstring", hexstring)
            );
            //Set up the Ravcencoin Object
            ServerCommand request = new ServerCommand()
            {
                commandId = "0",
                commandMethod = "decoderawtransaction",
                commandParams = commandParams,
                commandJsonRpc = "2.0"
            };

            //Get the hex string of the transaction back from getrawtransaction, and then parse it to get just the raw hex string from result
            ServerResponse response = await RpcConnections.RavenCore.Connect(request, connection);

            return response;
        }
        /// <summary>
        /// Looks up the raw transaction and then decodes the raw transaction to return a verbose json response about the transactionid
        /// </summary>
        /// <param name="txid"></param>
        /// <param name="connection"></param>
        /// <returns>See return details from "DecodeRawTransaction"</returns>
        public static async Task<ServerResponse> GetPublicTransaction(string txid, ServerConnection connection)
        {
            //Get the RawTransaction and return the hexcode
            ServerResponse hexcode = await GetRawTransaction(txid, connection);

            //Get Full Transaction from Hexcode
            ServerResponse response = await DecodeRawTransaction(hexcode.responseContent, connection);

            return response;
        }
    }
}

You can see this code is very similar to what we did last time – but this time instead of making code that gets the hexdata from the txid and decodes it in the same function, we have 3 functions:

  • GetRawTransaction (Returns the hexcode of the transaction from the txid)
  • DecodeRawTransaction (Returns the transaction details from the hexcode)
  • GetPublicTransaction (Calls GetRawTransaction and then DecodeRawTransaction and returns the transaction details)

Why do is this way? Well, we may want to use GetRawTransaction and DecodeRawTransaction on their own for something else, so why write it twice?

This is all we have to do for now inside Ravencoin.ApplicationCore. Hopefully you have no errors right now.


Hooking up Ravencoin.Web to use Ravencoin.ApplicationCore

OK, so we made these models, functions etc in Ravencoin.ApplicationCore. We now need to add a dependency in Ravencore.Web to be able to use them.

Right click on the “Dependencies” item under Ravencore.Web. Choose “Add Project Reference”.

Choose Ravencoin.ApplicationCore by clicking the checkbox, and then hit OK.


Adding Server Configuration and Logging

Remember how we had our connection details hard coded into the class that called the RPC Server? That’s a thing of the past now. We’re going to store it in appsetting.json in the root of your Ravencoin.Web project. We’ll then use Dependency injection into each of our controllers to read this info.

Your appsettings.json should look like this (but with your own connection details of course):

{
  "ServerConnection": {
    "host": "127.0.0.1",
    "port": "8766",
    "username": "cryptobullsh",
    "password": "test12311"
  },
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft": "Warning",
      "Microsoft.Hosting.Lifetime": "Information"
    }
  },
  "AllowedHosts": "*"
}

Now, in your Startup.cs we’re going to add a line that tells our application that this section of the config gets mapped to a ServerConnection Object. On lines 17 and 21 You can see we’re using configuring the service to reference the appsettings.json and Configure it.

using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Ravencoin.ApplicationCore.Models;

namespace Ravencoin.Web
{
    public class Startup
    {
        public Startup(IConfiguration configuration)
        {
            Configuration = configuration;
        }
      
        public IConfiguration Configuration { get; }
        // This method gets called by the runtime. Use this method to add services to the container.
        public void ConfigureServices(IServiceCollection services)
        {
            services.Configure<ServerConnection>(Configuration.GetSection("ServerConnection"));
            services.AddControllers();
        }
        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        { 
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }
            app.UseHttpsRedirection();
            app.UseRouting();
            app.UseAuthorization();
            app.UseEndpoints(endpoints =>
            {
                endpoints.MapControllers();
            });  
        }
    }
}

Now, lets open up Program.cs in the root of Ravencoin.Web. We’re going to add the logging provider into here.

using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;


namespace Ravencoin.Web
{
    public class Program
    {
        public static void Main(string[] args)
        {
            CreateHostBuilder(args).Build().Run();
        }
        public static IHostBuilder CreateHostBuilder(string[] args) =>
            Host.CreateDefaultBuilder(args)
                .ConfigureWebHostDefaults(webBuilder =>
                {
                    webBuilder.UseStartup<Startup>();
                })
                .ConfigureLogging(logging =>
                {
                    // clear default logging providers
                    logging.ClearProviders();

                    // add built-in providers manually, as needed 
                    logging.AddConsole();
                    logging.AddDebug();
                    logging.AddEventLog();
                    logging.AddEventSourceLogger();
                });
    }
}

A little more housecleaning…

We have some files that are useless or will be recreated with different names. Delete these files:

  • Ravencore.cs
  • RavencoinRequest.cs
  • /Controllers/GetAssetDataController.cs
  • /Controllers/GetBlockchainInfoController.cs
  • /Controllers/GetBlockchainInfoController.cs

Cool. We should be good to start implementing the simplified web code into some new controllers now.


Creating new Controllers

One of the design principles I wanted was to combine operations into a single controller. If its a Blockchain request it would go to the blockchain controller, if its an asset request, it goes to the assets controller and so on. We can have multiple actions within one controller.

Lets start with Blockchain. Right click Controllers and go to Add -> Class. Call it BlockchainController.cs.

using Microsoft.AspNetCore.Mvc;
using System.Threading.Tasks;
using Ravencoin.ApplicationCore.BusinessLogic;
using Ravencoin.ApplicationCore.Models;
using Microsoft.Extensions.Options;
using Microsoft.Extensions.Logging;
using System;

namespace Ravencoin.Web.Controllers
{
    [ApiController]
    [Route("/api/Blockchain/[action]")]
    public class BlockchainController : Controller
    {
        //Inject Server Configuration
        private readonly ILogger logger;
        private readonly IOptions<ServerConnection> serverConnection;
        public BlockchainController(IOptions<ServerConnection> serverConnection, ILogger<AssetsController> logger)
        {
            this.serverConnection = serverConnection;
            this.logger = logger;
        }

        public async Task<ServerResponse> GetBlockchainInfo(){
            logger.LogInformation($"Getting Blockchain Info");
            try{
                ServerResponse response = await Blockchain.GetBlockchainInfo(serverConnection.Value);
                return response;
            }
            catch (Exception ex){
                logger.LogError($"Exception: {ex.Message}");
                ServerResponse errResponse = new ServerResponse(){
                    statusCode = System.Net.HttpStatusCode.InternalServerError,
                    errorEx = ex.Message
                };
                return errResponse;
            }
        }

        public async Task<ServerResponse> GetBlockCount(){
            logger.LogInformation($"Getting latest block count");
            try{
                ServerResponse response = await Blockchain.GetBlockCount(serverConnection.Value);
                return response;
            }
            catch (Exception ex){
                logger.LogError($"Exception: {ex.Message}");
                ServerResponse errResponse = new ServerResponse(){
                    statusCode = System.Net.HttpStatusCode.InternalServerError,
                    errorEx = ex.Message
                };
                return errResponse;
            }
        }
    }
}

Lets start with what’s changed with the [Route] up on top. I’ve changed it to use /api/Blockchain/[action] instead of [controller]. This allows us to use either https://<host>/api/Blockchain/GetBlockchainInfo and https://<host>/api/Blockchain/GetBlockCount as different calls in the same URL structure even though those functions are in the same controller.

Up next in lines 16-22, we’re using dependency injection to bring the ServerConnection and the Logging objects we created earlier.

If we didn’t have all the logging and the try/catch blocks, the only thing we need to get the blockchain info is a single line now – ServerResponse response = await Blockchain.GetBlockchainInfo(serverConnection.Value);

Much simpler right?


Writing the other controllers

Right click the Controllers Folder, and click Add -> New Class. Name it Assets.cs

using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Options;
using Ravencoin.ApplicationCore.Models;
using Ravencoin.ApplicationCore.BusinessLogic;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using System;

namespace Ravencoin.Web.Controllers
{
    [ApiController]
    [Route("/api/Assets/[action]")]
    public class AssetsController : Controller
    {
        private readonly ILogger logger;

        //Inject Server Configuration
        private readonly IOptions<ServerConnection> serverConnection;
        public AssetsController(IOptions<ServerConnection> serverConnection, ILogger<AssetsController> logger)
        {
            this.serverConnection = serverConnection;
            this.logger = logger;
        }

        public async Task<ServerResponse> GetAssetData(string asset){
            logger.LogInformation($"Getting Asset data for {asset}");
            try{
                ServerResponse response = await Assets.GetAssetData(asset, serverConnection.Value);
                return response;
            } catch (Exception ex){
                logger.LogError($"Exception: {ex.Message}");
                ServerResponse errResponse = new ServerResponse(){
                    statusCode = System.Net.HttpStatusCode.InternalServerError,
                    errorEx = ex.Message
                };
                return errResponse;
            }
        }
    }
}

Right click the Controllers Folder, and click Add -> New Class. Name it Transactions.cs

using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Ravencoin.ApplicationCore.Models;
using Ravencoin.ApplicationCore.BusinessLogic;
using System.Threading.Tasks;
using System;

namespace Ravencoin.Web.Controllers
{
    [ApiController]
    [Route("api/Transactions/[action]")]
    public class TransactionController : ControllerBase
    {
        private readonly ILogger logger;

        //Inject Server Configuration
        private readonly IOptions<ServerConnection> serverConnection;
        public TransactionController(IOptions<ServerConnection> serverConnection, ILogger<TransactionController> logger){
            this.serverConnection = serverConnection;
            this.logger = logger;
        }

        [HttpGet]
        public async Task<ServerResponse> GetPublicTransaction(string txid)
        {
            logger.LogInformation($"Getting Transaction data for {txid}");
            try{
                ServerResponse response = await Transactions.GetPublicTransaction(txid, serverConnection.Value);
                return response;
            }
            catch (Exception ex){
                logger.LogError($"Exception: {ex.Message}");
                ServerResponse errResponse = new ServerResponse(){
                    statusCode = System.Net.HttpStatusCode.InternalServerError,
                    errorEx = ex.Message
                };
                return errResponse;
            }
        }
    }
}

Testing it out

OK lets test out what we did. Clock the little play icon to start the application.

Blockchain Operations

Lets head over to https://<host>/api/Blockchain/GetBlockchainInfo:

And now to https://<host>/api/Blockchain/GetBlockCount:

Asset Operations

Head over to https://<host>/api/Assets/GetAssetData?asset=TRASH

Transaction Operations

Head over to https://<host>/api/Transactions/GetPublicTransaction?txid=a16923c2dd1ed12f2c022437fe54a4d5222ed0f8dacecc9223c69efd27d32c61

Cool, everything works.


A note about errors

Notice we’re also not just returning the json from the Ravencore RPC server now. We’re returning one of our own models we made earlier, called ServerResponse, where we include data like the status code, if there is an error, and of course the content itself. We could later do things like add a property about how long the request took if we wanted, etc.

As an example of an error, if i put in the wrong password for the rpc server in my config file, we’ll see this:

Just to note, all errors coming from Ravencore.cs will be 400. This is due to Microsoft not exposing statusCode in the HttpRequestException class. It’s on my //Todo’s to figure out a better way to handle different exceptions – but it’s a little pointless since the Ravencore RPC server doesn’t really give us any meaningful errors anyway. Most errors are simply “Response status code does not indicate success: 500 (Internal Server Error).”.


Did I help? If you’re feeling generous, buy me a coffee by sending RVN to RHEH92NguBjaxXQPsM1bedkqqTXKr9EZcM

Intro to Ravencoin Development

This… is going to be a long one.

All code examples are available on Github,

I noticed when looking around ravencoin subreddits and googling, there is very little information on how to actually develop software against a ravencoin node. Ravencoin is a fork of bitcoin so there should be plenty of information on this right? Well, even that led me down some rabbit holes that didn’t even get me the basic connectivity to a node.

So I decided I’d attempt to provide a newbie-type guide to get basic connectivity configured to a Raven Core Wallet/Node, do some test calls using Postman, and then make a project to call it in code.


Setting up your Ravencoin Wallet as a full server node:

Go and get yourself the official Ravencoin wallet from https://www.ravencoin.org/wallet and install it/set up your wallet.

It’s going to download the ravencoin blockchain, which is a long process – We’re going to add things to the config that will require it to do that a 2nd time, so if this is your first time setting it up you can save yourself some time here. If you already had a synced blockchain – sorry, this is going to take a day.

First thing we’re going to do is click the Wallet menu, and go to Options. You’ll be presented with this screen:

Click Open Configuration File. It will give you a warning, Click OK.

You should now have a blank text file open. You need to put the following lines in. I added some comments about what these do, since there wasn’t much documentation out there about them.

#Accept cli/json RPC
server=1

#Maintains the full transaction index on your node. Needed if you call getrawtransaction. Default is 0.
txindex=1

#Maintains the full Address index on your node. Needed if you call getaddress* calls. Default is 0.
addressindex=1

#Maintains the full Asset index on your node. Needed if you call getassetdata. Default is 0.
assetindex=1

#Maintains the full Timestamp index on your node. Default is 0.
timestampindex=1

#Maintains the full Spent index on your node. Default is 0.
spentindex=1

#Username and password - You can make this whatever you want.
rpcuser=cryptobullsh
rpcpassword=test12311

#What IP address is allowed to make calls to the RPC server. If youre running the wallet on the same machine youre
#developing on, this is fine. If not, you need to put the IP address of the machine CALLING the node here.
rpcallowip=127.0.0.1

Save the file and close out your text editor after putting this info in. Restart Raven Core. It will tell you it needs to reindex everything.

The reason it needs to reindex everything, is now you are storing every transaction, asset etc on the blockchain on your computer, not just your own transactions. This will take up significantly more space on your HD. At the current time of writing, the size on disk for me is 42GB. Plan accordingly.

If everything went well, you won’t really see any difference to the ravencore wallet. Behind the scenes however, we’ve now downloaded the entire blockchain, opened port 8766 for RPC transactions and converted this wallet into a ravencore server. Whooo!


Testing connectivity over RPC/HTTP

Cool, we got a node acting as a server now. How do we make sure we can talk to it?

First, download Postman – https://www.postman.com/downloads/. I like postman for things like this. You could do this with Curl or Invoke-WebRequest powershell, but visually, postman is good.

Set your request type to POST, then enter the address you want to connect to. This will be your username and password you put in your raven.conf, along with your host (localhost in my case) and port (8766).

So your host should look like this if you used my example in your raven.conf:

http://cryptobullsh:test12311@127.0.0.1:8766

Click the Body option under the address, set it to “raw” and choose “JSON” as the content type. In the body, use this as an example:

{"id": 0, "method": "getblockchaininfo", "params": [], "jsonrpc": "2.0"}

Click “Send” If all goes well, you’ll see the output like the one below, and you’ve now successfully connected to your Ravencoin server!


Setting up your Development Environment and Project

The example I’m going to write is in C#. I like C#, but it’s not for everyone. However, yo could probably take my code example and do the same thing in Python, Java, etc. It’s not super complicated code.

First, if you don’t have Visual Studio 2019 installed, go on over to https://visualstudio.microsoft.com/ and click Download-> Visual Studio 2019 Community. It’s free.

Once installed, Create a new WebAPI project. Choose ASP.Net Core WebApp (Model-View-Controller):

Now, Name it something and choose a location for the code. I used RavencoinApiExample, you can too if you like.

Choose .Net Core 3.1, and none for authentication. You can configure for HTTPS if you like, it will ask you to install a self-signed certificate for this to work.

OK, you should have a new project with some boilerplate code. First thing we’re going to want to do is install Newtonsoft.Json package in our project.

In the solution Explorer, right click the Project and choose “Manage NuGet Packages”

Click Browse and search for Newtonsoft.Json. Install that.

OK, Let’s start writing some code!!

Disclaimer – I’m not an awesome developer. I know enough to make me dangerous. This code is probably not the best optimized code, and obviously I should keep hosts/usernames/passwords etc in protected config files. This is the barebones to get everyone with communicating with ravencoin. Use at your own risk.

First thing we’re going to do is set up a class, which will serve us as a definition of the object we’re going to be passing around for each request.

Right click your RavencoinApiExample Project, and go to Add->Class. (Or Shift-Alt-C). Call it RavencoinRequest.cs

Inside this file, we’re going to define some properties. Additionally, since the ravencoin server uses property names that are considered protected (such as “params”), we’re going to use the Newtonsoft Json library to annotate we want a different name for these properties when we serialize the json.

using System.Collections.Generic;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
namespace RavencoinApiExample
{
    public class RavencoinRequest
    {
        [JsonProperty(PropertyName = "id")]
        public string reqid { get; set; }

        [JsonProperty(PropertyName = "method")]
        public string reqmethod { get; set; }

        [JsonProperty(PropertyName = "params")]
        public JObject reqparams{get;set;}

        [JsonProperty(PropertyName = "jsonrpc")]
        public string reqjsonrpc { get; set; }

    }
}

Notice how the name of the property in c# is “reqid” for example, but we’re telling Newtonsoft.Json to consider it “id”.

One important thing of note, is the “reqparameters” type is a JObject. You might ask – why not use a dictionary etc? I did at first. Then I realized serializing the json directly is easier. Also there might be a node command that only takes one value instead of a key/value pair. If you’re using a dictionary, that makes it pretty annoying to work with.


Now, we’re going create a class that will actually interact with the server. Right click your RavencoinApiExample Project, and go to Add->Class. (Or Shift-Alt-C). Call it Ravencore.cs

We’re going to define a method called “Connect” here, that will take the previously created RavencoinRequest Object and use the data to execute something on the ravencoin node. Similar to what we did in postman, this is doing something very similar – it’s using a HttpClient to set up the connection to the node, set the right headers, set up authentication and send the body of the request.

using System;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using Newtonsoft.Json;

namespace RavencoinApiExample
{
    public class Ravencore
    {
        static string hostName = "127.0.0.1";
        static string port = "8766";
        static string userName = "cryptobullsh";
        static string password = "test12311";

        public static string Connect(RavencoinRequest rreq)
        {
            HttpClient client = new HttpClient();
            Uri baseUri = new Uri($"http://{hostName}:{port}");
            client.BaseAddress = baseUri;
            client.DefaultRequestHeaders.Clear();
            client.DefaultRequestHeaders.ConnectionClose = true;
            client.DefaultRequestHeaders.Add("Accept", "application/json");

            //Set up authentication
            var authenticationString = $"{userName}:{password}";
            var base64EncodedAuthenticationString = Convert.ToBase64String(System.Text.ASCIIEncoding.ASCII.GetBytes(authenticationString));

            //Set up the body of the message.
            //This adds the authorization header and encoding the RavencoinRequest object into json.
            var requestMessage = new HttpRequestMessage(HttpMethod.Post, "/");
            requestMessage.Headers.Authorization = new AuthenticationHeaderValue("Basic", base64EncodedAuthenticationString);

            //Set the content of the body
            requestMessage.Content = new StringContent(JsonConvert.SerializeObject(rreq), Encoding.UTF8, "application/json");

            //make the request
            var task = client.SendAsync(requestMessage);
            var response = task.Result;
            response.EnsureSuccessStatusCode();
            string responseBody = response.Content.ReadAsStringAsync().Result;

            return responseBody;
        }
    }
}

So now we have an object with defined properties that we pass to this method that will connect to the node and return a string of data back.


Now, we need to define Controllers for our WebAPI to listen to requests to then pass to this method.

Let’s do that now. Right click your “Controllers” Directory, and go to Add->Class. (Or Shift-Alt-C). Call it GetBlockchainInfo.cs.

Pay close attention to the “using” block, we’re specifically using Microsoft.AspNetCore.Mvc libraries here. Also note that we’re defining this is an ApiController and we want to route to [controller]. What this means is, whatever you name your Controller file, whatever comes before the word “Controller” is going to be your route (example: this would be http://localhost/GetBlockchainInfo).

On this controller, we’re going to do exactly what we did in Postman. We’re going to create a new RavencoinRequest Object, give it some data, and then send that Object over to Ravencore.Connect and return the response to the browser/consumer.

using Microsoft.AspNetCore.Mvc;

namespace RavencoinApiExample.Controllers
{
    [ApiController]
    [Route("[controller]")]
    public class GetBlockchainInfoController
    {
        public string Get()
        {
            //Set up the Ravcencoin Object
            RavencoinRequest request = new RavencoinRequest()
            {
                reqid = "0",
                reqmethod = "getblockchaininfo",
                reqjsonrpc = "2.0"
            };

            //Send the Ravencoin Request Object to the Method that will call the node
            string response = Ravencore.Connect(request);

            return response;
        }
    }
}

This is the barebones of what we need to do to make this work. Lets test it out, shall we? Click the “Play” button and a browser will come up.

It may go to the default example url. We want to go to https://<hostname:port>/GetBlockchainInfo

If you got the blockchaininfo result, you were successful!! Good job.


You may have noticed, we didn’t put anything in the params. That’s because we don’t need to for getblockchaininfo, its a command that doesnt require any parameters.

So, lets make a controller that does require some parameters.

Let’s do that now. Right click your “Controllers” Directory, and go to Add->Class. (Or Shift-Alt-C). Call it GetAssetDataController.cs. We’re going to look up some assets.

using Microsoft.AspNetCore.Mvc;
using Newtonsoft.Json.Linq;

namespace RavencoinApiExample.Controllers
{
    [ApiController]
    [Route("[controller]")]
    public class GetAssetDataController
    {
        public string Get(string asset)
        {
            //Set up any parameters we may need
            JObject parameters = new JObject();
            parameters.Add("asset_name", asset);

            //Set up the Ravencoin Object
            RavencoinRequest request = new RavencoinRequest()
            {
                reqid = "0",
                reqmethod = "getassetdata",
                reqparams = parameters,
                reqjsonrpc = "2.0"
            };

            //Send the Ravencoin Request Object to the Method that will call the node
            string response = Ravencore.Connect(request);

            return response;
        }
    }
}

You can see here we’re setting up a JObject with a parameter we’re getting from the querystring, adding those parameters to our Ravencoin object, and passing it off to Ravencore.Connect.

Start your application again, and when the browser comes up, lets look at a random asset. This time we want to go to https://<hostname:port>/GetAsetData?asset_name=TRASH

You should see this response. If you did, You now know how to pass parameters into your request! Good job.

OK, how about something a bit more challenging? I was figuring this one out on my own, and it drove me up the wall a bit. I started writing code to do “gettransaction”, and it worked fine on one of my own transactions – but failed when looking up someone else’s transaction. WHY? Well, according to the documentation “Get detailed information about in-wallet transaction <txid>” Well shit, that’s not very useful.

Well, we need to do two calls to get that information – one to “getrawtransaction” with the transaction ID as a parameter, which returns the hex string of the transaction, which then we’ll use as a parameter to call decoderawtransaction. FUN!

So, lets do this. Right click your “Controllers” Directory, and go to Add->Class. (Or Shift-Alt-C). Call it GetTransactionController.cs. and throw this code in there:

using Microsoft.AspNetCore.Mvc;
using System.Collections.Generic;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
namespace RavencoinApiExample.Controllers
{
    [ApiController]
    [Route("[controller]")]
    public class GetTransactionController : ControllerBase
    {
        [HttpGet]
        public string Get(string txid)
        {
            //Set up parameters to get the hex string of the transaction
            JObject rawtxparameters = new JObject(
                new JProperty("txid", txid)
            );
            //Set up the Ravcencoin Object
            RavencoinRequest rawtxrequest = new RavencoinRequest()
            {
                reqid = "0",
                reqmethod = "getrawtransaction",
                reqparams = rawtxparameters,
                reqjsonrpc = "2.0"
            };

            //Get the hex string of the transaction back from getrawtransaction, and then parse it to get just the raw hex string from result
            JObject rawtxresponse = JObject.Parse((Ravencore.Connect(rawtxrequest)));
            JToken hexstring = rawtxresponse["result"];

            //Add the hex string to the next call to decode the raw transaction
            JObject decodetxparameters = new JObject(
                new JProperty("hexstring", hexstring)
            );

            //Set up the Ravcencoin Object
            RavencoinRequest request = new RavencoinRequest()
            {
                reqid = "0",
                reqmethod = "decoderawtransaction",
                reqparams = decodetxparameters,
                reqjsonrpc = "2.0"
            };

            //Get the Raw Transaction details for the transaction ID from the server.
            string response = Ravencore.Connect(request);

            return response;
        }
    }
}

Can you see what I’m doing here? The response from the first call is going directly into a JObject, which then gets parsed out and used to the subsequent call to get the result we want.

Let’s test this. I’m going to pull a random transaction from the blockchain explorer of someone on the “rich list” for example (a16923c2dd1ed12f2c022437fe54a4d5222ed0f8dacecc9223c69efd27d32c61).

Click the play button again to start the application. This time, we want to go to https://<hostname:port>/GetTransaction?txid= a16923c2dd1ed12f2c022437fe54a4d5222ed0f8dacecc9223c69efd27d32c61

Ah, it feels so good when things just work…. (but for real, I refactored this code like 40 times, lol).

Did I help? If you’re feeling generous, buy me a coffee by sending RVN to RHEH92NguBjaxXQPsM1bedkqqTXKr9EZcM

How to build a Ravencoin Full Node in Docker (With Synology)

I’ve come to love the Ravencoin project, the community and all it stands for. I wanted to help out by running a full node to support the network.

I didn’t want to set up a Raspberry Pi, or run it on one of my desktops though. I tend to run software like this in docker running on my Synology NAS. Searching around, I couldn’t seem to find any images on dockerhub that I would feel comfortable running.

I inquired on Reddit, and Jeroz_ pointed me to a dockerfile that already existed on Ravencoin core’s github, so this naturally made me screw around and get this working on my own.

The easiest path to building an image (at least for me) is as follows:

Creating the Docker Image

If you’re doing this on a Synology NAS, you’ll need to install Docker, and Enable SSH on your NAS. (Temporarily at least, I would not leave this enabled)

This can be found in Control Panel -> Terminal & SNMP -> Enable SSH Service

  1. Download the entire source from https://github.com/RavenProject/Ravencoin
  2. Extract it. If you’re doing this on a Synology NAS like I was, copy the contents into a file share.
  3. Move the Dockerfile from /contrib to the root of the Ravencoin directory (one level up), since the Dockerfile references autogen.sh. It will fail otherwise, and i’m too stupid and inexperienced with docker to find out what allows it to build without moving the file πŸ˜‰
  4. SSH to your NAS, cd to the directory you uploaded the Ravencoin source to on your NAS, and run “sudo docker build -t ravencore .” (don’t forget the period at the end) to create the image.
  5. You should see the image compile. This may take a while.

The build process should start with this:

And finish like this:

Still with me? Cool. We have an image now that we can launch.

If you made this image on another machine, you can gzip it up and transfer to another machine by using sudo docker save ravencore:latest | gzip > ravencore.tar.gz. You can Import the tar.gz file in Docker -> Images->Add By File.

But if you did all this on your Synology CLI, the image will show up in Docker, under Images:

Click this Image, click Launch, and then click Advanced Settings:

Click Auto-Restart (If something crashes, it will auto restart the image)

Click the “Network” Tab, and then click “Use the same network as Docker Host”. (This works in my scenario – you may have a separate network you want to use)

Apply all these settings, and Launch this puppy!

OK, how do I know what its doing?

The logs outputting to the console aren’t very helpful, though you can see it’s discovering peers and such.

If you’d like to see the status of the blockchain syncing, we’ll have to break into the image’s CLI and run raven-cli to get some more info. For the first hour or so you may only be syncing headers. you can see this by running sudo docker exec -it Ravencore raven-cli getblockchaininfo

The result will look like this:

You can continue to run this command to ensure the headers are increasing. When they’re done syncing you should see the blocks increase.

What about Networking and Port forwarding?

You may have noticed your node doesn’t show up on https://www.ravennodes.com/nodes/. You’ll likely need to forward TCP/8767 from your WAN interface to the internal IP if your docker host. Since there are many types of routers out there, google your router model and look up how to forward ports.

After you’ve completed this, you can test the port is open by using https://www.yougetsignal.com/tools/open-ports/ and it should automatically detect your external IP. Put in port 8767 in the Port textbox and click Check.

If everything went well, you should see it open, and your IP show up on RavenNodes.

That’s it!!! Holy shit, we did it everyone. Good job.

Disclaimer: What works for me may not work for you. I’m also no expert on docker but enough to make me dangerous, so take my my advice with a grain of salt. If something screws up your machine, not my fault! be warned.

Did I help? If you’re feeling generous, buy me a coffee by sending RVN to RHEH92NguBjaxXQPsM1bedkqqTXKr9EZcM