The Version 2.x.x series was officially released on the 8th of November of this year after an intensive short Beta testing period. The release was very successful not only because of the feedback but because it only contained 2 non critical bugs, not bad for 15,000 + lines of completely new code designed from scratch. Kudos to the Beta testing team!

We are now in condition to apply some basic concepts which were detailed in my previous blog post. We will make a custom component using just a text editor as our primary tool. However it is strongly suggested to download Qt Creator because it has a powerful highlighting system and syntax corrector for the script language we are going to use.

The script language that WBC (Worldcoin Business Center) relies on for components is called QML and was designed by the same guys who developed the top notch library Qt. An estimated 90% of Bitcoin and altcoin wallets use the QT library, we are the first and only ones who are using this new QML technology. QML is very easy to learn and it uses a fairly large subset of Javascript that most web designers are familiar with.

Step 1: Define our goal

In this exercise we will attempt something relatively ambitious that will highlight many areas of custom development that WBC is capable of. This is still an introduction, there are many more areas, functions and possibilities that we won’t even scratch in this mini tutorial yet.

We will develop a component which retrieves the last transactions from the wallet, converts them to american dollars and calculates the VAT according to a predefined parameter. Also we want to refresh automatically the transaction list whenever the exchange rate changes, all this in less than 100 lines of code. All of the code will be pasted at the end of this article.

Step 2: Declare the new component

When WBC is launched it doesn’t load all the components available, it just loads the ones you start using and their dependencies. ‘on-demand dynamic load’ allows WBC to handle hundreds of components while still being very fast and memory friendly. Keeping this in mind, we need a way to send the engine a very basic set of information so it can display the available components on the Module Panel.

In the directory where you installed WBC 2.x.x create a file called WorldcoinCustomComponents.cfg and insert this text:

[ComponentSuperTransactionList]
Online=0
Label=Super Transaction List
Area=Super Area
Module=Super Module
Type=Report
Dependencies=ComponentWalletsSummary

(The engine is case sensitive so be careful!)

The text between brackets is the name of our new component, by priniciple all component names should start with the ‘Component’ prefix. The second line is reserved for components that will use online services provided by our team. The third line is the label that will be shown in the panel, we will use the cool name ‘Super Transaction List’ for this example. The next two are the Area and the Module that this component will belong to; if these do not already exist the engine will create them for you. The ‘Type’ tag specifies what type of component we are creating. There are several types that are coded with a color in the panel, for this one we will use the ‘Report’ type. Finally the ‘Dependencies’ tag specifies which component should the engine load before it loads our component when activating it. In this case ComponentWalletsSummary will provide us with the rate conversion we will need further on.

Step 3: Define the new component

Here we start with real code. For this you need to create a file called:

/Components/Custom/ComponentSuperTransactionList/ComponentSuperTransactionList.qml

We need to put our component into the ‘Custom’ sub directory because when upgrading, the installer will override other directories and leave this one alone.

Lets start coding! (You can copy and paste)

import QtQuick 2.4
import QtQuick.Controls 1.3
import QtQuick.Controls.Styles 1.4
import "../../../WFUserInterface/AXLib"
import SStyleSheet.Lib 1.0
import ACMeasures.Lib 1.0
import WFDefinitions.Lib 1.0

AXComponent {
id: rcRoot
reHeightCm: 8
reWidthCm: 16

property string srCoin: "WDC"
property real reTransactionCount: 10
property real reVAT: 0.13
function fuSetup() {}

function fuActivate() { fRawCallRequested(srCoin, "listtransactions "*" " + reTransactionCount, 0) }
}

The first block of lines are used to import libraries that contain useful already made functions, you can use this set for most projects, more details will come in the next blog!

The next section defines our main component. We will make a brief introduction on how basic QML works to help you understand it. Apart from the import section, all qml files need a main object called ‘the root’ object. There are many predefined by QML that you can use as basic blocks to build your own, amusingly, the over abused Lego metaphor works very well here. Each object has it’s own predefined set of properties and some others that you define yourself. It can also have a set of functions where you are able to insert Javascript code to perform non trivial calculations.

Let’s analyze this line by line:

AXComponent {

This is our predefined root object, for all the components you develop this should be the main object created:

id: rcRoot

The property ‘id’ defines the name of our main object:

reHeightCm: 8
reWidthCm: 16

These two lines define the default size of our component. Please note that the measure unit used is Centimeters so the engine will draw the component with (approximately) the same visual size in different setups.

property string srCoin: "WDC"
property real reTransactionCount: 10
property real reVAT: 0.13

These three lines define custom properties, in this case a string property called srCoin with the value “WDC”. There is also a number property which will contain the default number of transactions that will be retrieved (10 in this case). And lastly, a property that will hold our VAT percentage.

function fuSetup() { }


function fuActivate() { fRawCallRequested(srCoin, "listtransactions "*" " + reTransactionCount, 0) }

The next two lines define custom functions, the first line is called only the first time this component is called. The second line is called every time this component is called from the Module Panel. fuActivate() uses a predefined function called fRawCallRequested. This function contains a command that is sent to the daemon as is, unprocessed. In this case we are sending the command:

listtransactions "*" 10

Okay, so lets see our progress! Save the file and start WBC. Click in the new component created, you should see something like this:

WBC custom component 3.1

WBC custom component 3.1

If a sub window does not appear then you have made a mistake in syntax, to be sure just copy and paste the code shown above.

Cool! let’s move on, insert the following code below the line that contains: 'property real reTransactionCount: 10'


ListModel {
id: lmTransactions
}

Broadly speaking it is considered a very good practice in any language to separate the data from the visual presentation, this way you can alter the UI without touching the underlaying data. In Qt the objects where you store data are called ‘models’. The snippet above represents the object where we will put our transaction data (ListModel in this case). Because it starts empty we only give it an ID.

Next we need to create the object responsible to display our data, as it will be displayed as a table, TableView is the best candidate for the task.

TableView {
id: tvTransactions
anchors.fill: rcRoot
model: lmTransactionsTableViewColumn {
role: "miTransactionType"
title: "Type"
width: ACMeasures.fuToDots(2)
}
TableViewColumn {
role: "miAddress"
title: "Address"
width: ACMeasures.fuToDots(7)
}
TableViewColumn {
role: "miAmount"
title: "Amount"
width: ACMeasures.fuToDots(3)
}
TableViewColumn {
role: "miUsd"
title: "USD"
width: ACMeasures.fuToDots(3)
}
TableViewColumn {
role: "miVAT"
title: "VAT"
width: ACMeasures.fuToDots(3)
}
}

Two new properties:

anchors.fill: rcRoot With this property we are telling the engine that the table should be anchored, filling the main component (rcRoot) which helps to occupy all the visual space that the root component uses. If the window gets bigger then the table view will get bigger too. Here we can start noticing the power of the language! We are telling the engine what to do and not how to do it. In many other languages if the main window is resized then you should manually resize the child items to maintain the visual aspect.

model: lmTransactions
This property will tell the view that it should use the data stored in the model defined before.

Inside the TableView object we introduce five child objects of type TableViewColumn which specifies the content type and title of each column.

The ‘role’ property establishes a name to be referenced by the engine. The ‘title’ represents the text to be displayed and finally the ‘width’ represents the horizontal size. This property is defined in pixels so we need to use our predefined function ACMeasures.fuToDots(N) which transforms N centimeters to pixels

Screenshot time!

WBC custom component 3.3

WBC custom component 3.3

So far so good …

Now lets populate our table, if you remember we already sent the proper command to retrieve the last transactions from the wallet. Now we should parse the answer data and put it in our model. Put the code below above the section with our custom functions.

Connections {
target: rcRoot
onSMessageArrivedJson: {
lmTransactions.clear()
for(var i = 0; i < lList.length; i++) {
var vaTransaction = lList[i]
var vaType = vaTransaction.category == "send" ? qsTr("Sent") : qsTr("Received")
var vaAddress = vaTransaction.address
var vaAmount = parseFloat(vaTransaction.amount)
var vaUsd = vaAmount * parseFloat(cComponentWalletsSummary.srExchangeRate)
var vaVAT = vaUsd * reVAT
lmTransactions.append({"miTransactionType": vaType, "miAddress": vaAddress, "miAmount": vaAmount, "miUsd": vaUsd, "miVAT": vaVAT })
}
}
}

When we send a request, the root component will receive it. Therefore we must connect the arrival to a function to handle the data. We will use the Connections object for this task and connect the onSMessageArrivedJson signal that is triggered when an request answer arrives.

Inside this function we first will erase the data of our previous model, then we iterate the predefined object that holds our data called lList. After that we should define the variables that hold the desired data and process them according to our goal. The function parseFloat converts a string to a number. Also notice that vaUsd needs to know the exchange rate factor. Instead of retrieving it ourselves which is a more complicated code, we will use a component that already does that. In this case ComponentWalletsSummary, and we call this the component prepending a ‘c’ (low caps). Specifying which property we want to use, srExchangeRate holds the exchange rate in USD.

Finally we append to our model all processed variables.

If you run the resulting code immediately after launching WBC, you will notice that the USD and VAT field are filled with zeros, this is because ComponentWalletSummary didn’t have enough time to retrieve the exchange rate from the network. You should close the component for a few seconds and try again.

Probably you would say: ‘That’s utterly crap’ hopefully in a more subtle way, and I agree, so let’s add a little more code that will automatically retrieve transactions every time the exchange rate changes.

Connections {
target: cComponentWalletsSummary
onSrExchangeRateChanged: { fuActivate() }
}

We make another connection, this time the signal we are waiting for is from the ComponentWalletsSummary component (don’t forget to prepend ‘c’). For every declared property, the engine automatically creates a function that handles the property when it is changed. The convention that is used is to prepend the prefix ‘on’ making sure the upper case is on the first letter of the property. Finally to append the suffix to ‘Changed’. In this case we are telling that when the exchange rate changes we can then call the activate function again (ie. request the list of transactions again).

Now launch WBC and click on our component, it will display the fields with zeros but now you can wait a few seconds and it will automatically fill the transaction list when exchange rate changes.

Done!

So what time is it? … Screeeenshot time!

WBC custom component 3.3

WBC custom component 3.3

70 Lines of code including brackets and some fluff, extremely small comparing with the amount of effort other wallets would need to do to achieve the same result.

There is much, much more we can achieve easily. For example we could directly define how to render each cell. We could put colors according to amounts, or maybe some images too … nahhh what the heck, we should aim to a higher crazy target … we could put whole video games in each cell if we wanted too (Hopefully you wouldn’t or your PC would suffer a little). The possibilities are endless!

With this mini tutorial I hopefully managed to show the power that our framework has from the development point of view. A lot of passive man power would be able to experiment and make their own components within WBC without being afraid of touching the kernel routines.

The heavy rough hippo is finally dancing, it’s time to dance with him!

Appendix: All exercise code


import QtQuick 2.4
import QtQuick.Controls 1.3
import QtQuick.Controls.Styles 1.4
import "../../../WFUserInterface/AXLib"
import SStyleSheet.Lib 1.0
import ACMeasures.Lib 1.0
import WFDefinitions.Lib 1.0
AXComponent {
id: rcRoot
reHeightCm: 8
reWidthCm: 18
property string srCoin: "WDC"
property real reTransactionCount: 10
property real reVAT: 0.13
ListModel {
id: lmTransactions
}
TableView {
id: tvTransactions
anchors.fill: rcRoot
model: lmTransactionsTableViewColumn {
role: "miTransactionType"
title: "Type"
width: ACMeasures.fuToDots(2)
}
TableViewColumn {
role: "miAddress"
title: "Address"
width: ACMeasures.fuToDots(7)
}
TableViewColumn {
role: "miAmount"
title: "Amount"
width: ACMeasures.fuToDots(3)
}
TableViewColumn {
role: "miUsd"
title: "USD"
width: ACMeasures.fuToDots(3)
}
TableViewColumn {
role: "miVAT"
title: "VAT"
width: ACMeasures.fuToDots(3)
}
}
Connections {
target: rcRoot
onSMessageArrivedJson: {
lmTransactions.clear()
for(var i = 0; i < lList.length; i++) {
var vaTransaction = lList[i]
var vaType = vaTransaction.category == "send" ? qsTr("Sent") : qsTr("Received")
var vaAddress = vaTransaction.address
var vaAmount = parseFloat(vaTransaction.amount)
var vaUsd = vaAmount * parseFloat(cComponentWalletsSummary.srExchangeRate)
var vaVAT = vaUsd * reVAT
lmTransactions.append({"miTransactionType": vaType, "miAddress": vaAddress, "miAmount": vaAmount, "miUsd": vaUsd, "miVAT": vaVAT })
}
}
}
Connections {
target: cComponentWalletsSummary
onSrExchangeRateChanged: { fuActivate() }
}
function fuSetup() { }
function fuActivate() { fRawCallRequested(srCoin, "listtransactions "*" " + reTransactionCount, 0) }
}

Please report spelling mistakes to contact@worldcoin.global

119