Previous part 3:
Recovering price quotations from an exchange data API
The IBlockExplorer API
This interface will allow us to query information about anything related to the addresses we manage.
It needs three variables to keep its state, the last JSON retrieved, the balance in satoshis and a list of funded addresses, the last two are meant to cache results so we don't need to recalculate them once we already have it.
Its functions are as follows:
load()
: gets a list of addresses and an API key (if needed) as a parameter and will retrieve information of those addresses from the REST API and save it in thelastJson
variable.getTotalBalance()
: after calling load this will return the total balance from the queried addresses.getFundedAddresses()
: a utility function that returns a list of the addresses that have UTXO, it will serve a purpose on later posts.
entry:
Goto(Type="Action Label", Label="function_load", If(%par1 ~ "function_load");
Goto(Type="Action Label", Label="function_getTotalBalance", If(%par1 ~ "function_getTotalBalance");
Goto(Type="Action Label", Label="function_getFundedAddresses", If(%par1 ~ "function_getFundedAddresses");
Return(Value="ERROR", Stop="On");
function_load:
ArrayClear(VariableArray="%args");
VariableSet(Name="%args", To="%par2");
VariableSplit(Name="%args", Splitter=";");
VariableSet(Name="%input_addresses", To="%args1");
VariableSet(Name="%input_apikey", To="%args2");
VariableSet(Name="%lastJson", To=0);
VariableClear(Name="%balanceSat");
VariableClear(Name="%fundedAddresses");
Return(Value="SUCCESS", Stop="On");
function_getTotalBalance:
Return(Value="%balanceSat", Stop="On");
function_getFundedAddresses:
Return(Value="%fundedAddresses", Stop="On");
What the fuck is happening in function_load?
As the Perform Task action only gets up to two parameters we will separate different parameters with the semicolon character ';
' and use the %args
variable to store the %par2
content and split it in an array where each element will have one parameter. This pretty much sums up the first five actions, that get the %par2 parameter and turns it into two different local variables: %input_addresses
and %input_apikey
.
Implementing the interface, the BlockChair case
Blockchair is one of the most used block explorers and has a very professional and convenient REST API, that makes retrieving information from the Blockchain really easy.
The steps used to retrieve information using the Blockchair API may be different on other Block Explorer API, but the idea behind it remains the same: Querying the sum for each UTXO value for each address under monitoring.
The first step will be cloning the IBlockExplorerAPI
task into a new BlockChairBlockExplorerAPI
task.
Retrieving your address information
As many BCH users use to have more than 20 addresses on their wallets thanks to the BIP39 address derivation, retrieving the information could be a very cumbersome task even programmatically, because of that pre-loading the JSON data once and treating it later is the most efficient strategy to follow.
entry:
( . . . )
function_load:
ArrayClear(VariableArray="%args");
VariableSet(Name="%args", To="%par2");
VariableSplit(Name="%args", Splitter=";");
VariableSet(Name="%input_addresses", To="%args1");
VariableSet(Name="%input_apikey", To="%args2");
HttpRequest(Method="GET", URL="https://api.blockchair.com/bitcoin-cash/dashboards/addresses/%input_addresses?key=%input_apikey");
Return(Value="ERROR : HTTP %http_response_code", Stop="On", If="%http_response_code > 299");
VariableSet(Name="%BlockChair_lastJson", To="%http_data");
VariableClear(Name="%BlockChair_balanceSat");
VariableClear(Name="%BlockChair_fundedAddresses");
Return(Value="SUCCESS", Stop="On");
function_getTotalBalance:
( . . . )
function_getFundedAddresses:
( . . . )
Step by step:
An HTTP Request to the
https://api.blockchair.com/bitcoin-cash/dashboards/addresses/
endpoint, to query a list of comma-separated CashAddr addresses%input_addresses
using an (optional if you are just doing your own experiments) API key?key=%input_apikey
Like in the last chapter, a conditional return if the HTTP response is an error
The Request data assignment to the JSON variable
Getting your account's balance
As soon as we have the JSON loaded we can start getting information from it, in this case, we are going to query and cache the balance in satoshi from the JSON.
entry:
( . . . )
function_load:
( . . . )
function_getTotalBalance:
If( %BlockChair_balanceSat !Set)
JavaScriptlet(Code={
var balance = JSON.parse(global('BlockChair_lastJson')).data.set.balance;
setLocal('%BlockChair_balanceSat', balance);
});
EndIf
Return(Value="%BlockChair_balanceSat", Stop="On");
function_getFundedAddresses:
( . . . )
Step by step:
If the
%BlockChair_balanceSat
isn't set...We run a JavascriptLet action that will retrieve the total balance from all addresses and set it into the variable.
Finally, we return the variable value.
Note that every time we load a new JSON, we unset the variable, so after loading a JSON the variable will need to be recalculated calling this function again.
Listing all addresses with UTXO
The Blockchair API, and in general, any other block explorer APIs have a consumption rate that can lead your IP to be blacklisted, temporarily blocked or if you have a paid subscription, charged for requests.
Because of that and because some of the APIs need to call each address individually you might want to know which addresses are funded.
entry:
( . . . )
function_load:
( . . . )
function_getTotalBalance:
( . . . )
function_getFundedAddresses:
If( %BlockChair_fundedAddresses !Set)
JavaScriptlet(Code={
var utxo = JSON.parse(global('BlockChair_lastJson')).data.utxo;
var addressList = "";
for(var i = 0 ; i < utxo.length; i++) {
if(addressList.localeCompare("") == 0) {
addressList = addressList.concat(",");
}
addressList = addressList.concat(utxo[i].address);
}
setGlobal('%BlockChair_fundedAddresses', addressList);
});
VariableSplit(Name="%BlockChair_fundedAddresses", Splitter=",");
ArrayProcess(VariableArray="%BlockChair_fundedAddresses", Type="Remove Duplicates");
VariableJoin(Name="%BlockChair_fundedAddresses", Joiner=",");
EndIf
Return(Value="%BlockChair_fundedAddresses", Stop="On");
Step by step:
If
%BlockChair_fundedAddresses
isn't setRun JavaScriptlet action that generates a list of addresses contained in every UTXO at the JSON and separate it by a comma.
As one address may contain more than one UTXO some duplicated may be found, so the variable is split, processed to eliminate duplicates and joined back again
Finally, we return the comma-separated list of addresses with UTXO
Testing our implementation
Time to write some tests to check how is this working.
BlockChair_functionLoad_success:
VariableClear(Name="%BlockChair_lastJson");
PerformTask(Name="BlockChairBlockExplorerAPI", Parameter1="function_load", Parameter2="%ALL_ADDRESSES;%APIKEY", ReturnVariable="%retval");
Return(Value="ERROR : %retval", Stop="On", If="%retval !~ SUCCESS");
BlockChair_getBalance_success:
PerformTask(Name="BlockChairBlockExplorerAPI", Parameter1="function_getTotalBalance", ReturnVariable="%retval");
Return(Value="ERROR : %retval", Stop="On", If="%retval eq 0 OR %retval !Set");
Notify(Title="Balance in satoshi", Text="%retval");
BlockChair_getFundedAddresses_success:
PerformTask(Name="BlockChairBlockExplorerAPI", Parameter1="function_getFundedAddresses", ReturnVariable="%retval");
Return(Value="ERROR : %retval", Stop="On", If="%retval eq 0 OR %retval !Set");
Notify(Title="List of funded addresses", Text="%retval");
The first test will load a JSON and check if returns SUCCESS, the second one will raise a push notification with your balance in satoshi, the third one will raise another notification with the list of funded addresses.
In the next part, we'll be preparing a notification builder object.
Special thanks to Blockchair.com for providing an API key.
Don't forget to subscribe, feedback will be appreciated!