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

Follow me on social media:

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes:

<a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>