Libbitcoin: Real Time Transaction Updates Using Subscribe_Address

Today I’m going to briefly go over a Libbitcoin example program. This tutorial will utilize libbitcoin-client’s subscribe_address function to watch for realtime updates on an addresses balance. As usual the example program will be written for the bitcoin testnet.

The first thing, as always, is to include the requisite libraries and namespaces.

#include <bitcoin/bitcoin.hpp>
#include <bitcoin/client.hpp>
#include <string.h>
#include <iostream>

using namespace bc;

Now, we will need to define a subscribe function, which will be void since we are not returning anything and takes the payment address we want to watch as an argument.

void subscribe(wallet::payment_address watch_address) {

}

Inside the subscribe function, we are going to use the connection_type object to define our client’s connection settings, including the testnet server endpoint we want to use. Then we are going to want to instantiate our obelisk client object.

void subscribe(wallet::payment_address watch_address) {

	client::connection_type connection = {};
	connection.retries = 3;
	connection.timeout_seconds = 8;
	connection.server = config::endpoint("tcp://testnet3.libbitcoin.net:19091");

	//instantiate obelisk client
	client::obelisk_client client(connection);

}

Once we have our connection established, we are going to need to implement 3 handlers for this function. First, we need to write an on_subscribed handler which will let us know that our subscription request succeeded. To do this we simply need to pass the watch_address variable and get the error code returned from our client call and output its associated message in our console output.

 	static const auto on_subscribed = [&watch_address](const code& ec) {
		std::cout << "Subscription to " << watch_address.encoded() <<": " << ec.message() << std::endl;
	};

Then, we need a second handler that will output any errors returned to our client.

	static const auto on_error = [](const code ec) {
		std::cout << "Error Code: " << ec.message() << std::endl;

	};

Now our third handler is going to be the on_update handler, this will be triggered every time we get an update about our address from our subscription.

For this, we simply need to get the transaction info returned to our client and output it to the console with an organized message. Each update should return an error code, a sequence number, the confirmed block height(0 if unconfirmed), and the hash of the transaction.

    auto on_update = [](const code& error,
        uint16_t sequence, size_t height, const hash_digest& tx_hash)
    {
    	std::cout << "New Transaction Recieved: " << std::endl;
	    std::cout << sequence <<": "<< "Hash: " << encode_hash(tx_hash) <<" Confirmed: "<< height << std::endl;
    };

Now, this handler will output the updates to the console, but is missing some vital information, such as the amount received in the transaction. For this, we are going to want to write another function, which looks up that information via the transaction hash we just received and displays the amount alongside this update.

So we are going to need to call another function, which we will write briefly, called getTXInfo. This new function is going to need some information like the connection settings and the address we are watching so we are going to pass those variables to the handler and then to our new function.

The on_update handler should look like this now:

    auto on_update = [&connection, &watch_address](const code& error,
        uint16_t sequence, size_t height, const hash_digest& tx_hash)
    {
    	std::cout << "New Transaction Recieved: " << std::endl;
	    std::cout << sequence <<": "<< "Hash: " << encode_hash(tx_hash) <<" Confirmed: "<< height << std::endl;
	    getTXInfo(connection, tx_hash, watch_address);
    };

Now, to finish up the subscribe function we just need to call some functions on our client object. First establishing a connection and setting our on_update handler like so:


if(!client.connect(connection))
{
std::cout << "Fail" << std::endl;
} else {
std::cout << "Connection Succeeded" << std::endl;
}

client.set_on_update(on_update);

then subscribing to our address and defining how many seconds we want to monitor the address for:

client.subscribe_address(on_error, on_subscribed, watch_address);
client.wait();
client.monitor(8000); //in seconds

All together, our subscribe function should look like this:

void subscribe(wallet::payment_address watch_address) {

	client::connection_type connection = {};
	connection.retries = 3;
	connection.timeout_seconds = 8;
	connection.server = config::endpoint("tcp://testnet3.libbitcoin.net:19091");
	//instantiate obelisk client
	client::obelisk_client client(connection);

	static const auto on_subscribed = [&watch_address](const code& ec) {
		std::cout << "Subscription to " << watch_address.encoded() <<": " << ec.message() << std::endl;
	};

	static const auto on_error = [](const code ec) {
		std::cout << "Error Code: " << ec.message() << std::endl;

	};

    auto on_update = [&connection, &watch_address](const code& error,
        uint16_t sequence, size_t height, const hash_digest& tx_hash)
    {
    	std::cout << "New Transaction Recieved: " << std::endl;
	    std::cout << sequence <<": "<< "Hash: " << encode_hash(tx_hash) <<" Confirmed: "<< height << std::endl;
	    getTXInfo(connection, tx_hash, watch_address);
    };

	if(!client.connect(connection))
	{
		std::cout << "Fail" << std::endl;
	} else {
		std::cout << "Connection Succeeded" << std::endl;
	}

	client.set_on_update(on_update);
	client.subscribe_address(on_error, on_subscribed, watch_address);
	client.wait();
	client.monitor(8000); //in seconds

}

Next, we need to implement the getTXInfo function to get the amount of the new output we received.

For this we are going to define another void function which takes the connection object, the transaction hash, and the payment address we are watching.

void getTXInfo(client::connection_type connection, const hash_digest tx_hash, wallet::payment_address watch_address) {

}

and again we want to instantiate a client object.


client::obelisk_client client(connection);

along with a simple error handler:

    auto on_error = [](const code& ec)
    {
    	std::cout << "Error Code: " << ec.message() << std::endl;
    };

Now, we are looking up a transaction object via the hash we just received from our update, so our on_done handler is going to need to take that transaction object and iterate through its outputs. For each output, we are going to want to extract the recipient address from the script(this is done using a special libbitcoin function included in the output object). Then we check the recipient address against our watch address and if they match we can print the value of that output to the console.


auto on_done = [&watch_address](const chain::transaction tx)
    {
    	for (chain::output out: tx.outputs())
    	{	
    		
    		if (wallet::payment_address(out.address().hash(), wallet::payment_address::testnet_p2kh) == watch_address)
    			std::cout << "Transaction value: "<< encode_base10(out.value(), 8) << std::endl;

    	}
    	   
    };

Note, that we need to convert the address to a testnet_p2kh format before checking against our watch address because the hash that is extracted from the script will initially be converted to a mainnet address.

Now that our handlers are done we can establish a connection to the client and call the fetch_transaction method like this:


if(!client.connect(connection))
{
		std::cout << "Fail" << std::endl;
	}
client.transaction_pool_fetch_transaction(on_error, on_done, tx_hash);
client.wait();
  


And the entire getTXInfo function should look like this:


void getTXInfo(client::connection_type connection, const hash_digest tx_hash, wallet::payment_address watch_address) {
	client::obelisk_client client(connection);

    auto on_done = [&watch_address](const chain::transaction tx)
    {
    	for (chain::output out: tx.outputs())
    	{	
    	
    		if (wallet::payment_address(out.address().hash(), wallet::payment_address::testnet_p2kh) == watch_address)
    			std::cout << "Transaction value: "<< encode_base10(out.value(), 8) << std::endl;

    	}
    	   
    };

    auto on_error = [](const code& ec)
    {
    	std::cout << "Error Code: " << ec.message() << std::endl;
    };

	if(!client.connect(connection))
	{
		std::cout << "Fail" << std::endl;
	}

    client.transaction_pool_fetch_transaction(on_error, on_done, tx_hash);
    client.wait();
}

Now, with both those function done we just need to call the subscribe function from main while passing the address we want to watch like this:


int main() {
	subscribe(wallet::payment_address("mnrnjVFimDFrNkszzMtecr4yrMKmEuMRbv"));
}

compile:


$ g++ -std=c++11 -o subscribe address_subscribe.cpp $(pkg-config --cflags libbitcoin --libs libbitcoin libbitcoin-client)

and run:


$ ./subscribe

The initial screen will show that you have successfully subscribed to the address, and then the console will hang until it gets an update or the monitoring time expires.

subscribe_initial(1)

If you jump over to https://testnet.manu.backend.hamburg/faucet you can request some testnet coins.

Subscribe_faucet(1)

Once they arrive, your program will notify you with a message showing the transaction hash and the amount received.

subscribe_console_update

As always, the code is on github

If you have any comments or tutorial requests please feel free to send them to me on twitter @alphajarm or by email aaron@jaramillo.xyz

Leave a Reply

Your email address will not be published.