Algorand Wallet Manager - Manage Your Own Wallets with a Graphical Interface
This solution will talk about the development of Algorand Wallet Manager which is a Python application that facilitates basic operations on an Algorand node.
This application offers a graphical user interface (GUI) built on top of Algorand’s Key Management Daemon (kmd). With it, users can graphically, instead of through the command-line, interact with kmd and perform some of the more common tasks on Algorand (e.g. operations with wallets/addresses and sending transactions). It also includes a contact list feature. Any user that is less technically inclined (or even a complete novice) can benefit from this application because all it requires is access to a running Algorand node and a Python interpreter. Though I would argue that anyone, even among the more experienced users, would prefer using a GUI for day-to-day use.
In summary if you own an Algorand node and you manage your own wallets and addresses, you may find this application useful to you.
Picking the language and technologies used in the development of an application is not an easy task. You have to strike the best balance between speed of development, familiarity with technologies, platform support, performance and final result. For this project I used Python with the following Python packages:
- py-algorand-sdk - The Python package for interacting with an Algorand node.
- PySide2 - A Python wrapper for Qt graphic interface libraries that were originally developed for C++ and are cross-platform.
- jsonpickle is a json serialization package that integrates well with custom Python objects (the json package only supports native data structures).
As far as prerequisite knowledge goes, if this is your first GUI application the main takeaways are:
- GUI event loop. Execution time must be lent back and forth between application logic and GUI management. This will be done by PySide2.
- Slots and signals. This is a slightly more expressive way to have events and callbacks.
- Multi-threading. This will be much needed when calling py-algorand-sdk because it’s never a good idea to borrow execution time from a GUI event loop for too long.
Algorand Wallet Manager is a classic GUI based on the main window / dialog window paradigm. The main window handles some functionalities and the widget to switch to any other dialog window.
The main window will take care of managing wallets and addresses and making sure the right conditions are met for a dialog to be displayed. One of such conditions might be that there needs to be a valid REST connection to an algod daemon for the transaction window to be displayed. The main window can simply disable the widget responsable for the transaction window until its condition is met and will do an equivalent action for other dialog windows.
The first thing the user will see is a list of wallets each of which can be unlocked with its password and will stay unlocked until either the application closes or the user locks it again. Once a wallet is unlocked the addresses within it can be explored and manipulated (all of this are kmd daemon operations and have no effect on the ledger).
The other dialog windows are: transaction window, settings window and contact list window.
The transaction window will let the user compile a valid transaction to be sent to the network through the connected node. Addresses from the contact list and all of the unlocked wallets can be used as a sender, receiver or closing address.
Three types of the most common transactions are supported: payment, asset opt-in, asset transfer (each one with the “close to” option).
The settings window just lets the user decide the method of connection to the algorand daemons. It supports bare REST endpoints, local directory or environment variables.
The contact window will memorize addresses and associate them with a nickname and a profile picture.
It should be noted that the address list will be the same across all connected nodes and all networks so be careful when sending algos or assets because you might be on the wrong network from when the address was memorized.
Qt libraries are historically one of the major graphical interface libraries for C++ and can do much more. With new versions they are now compatible with mobile and desktop platforms, they integrate with hardware such as bluetooth, NFC, fingerprint scanners and much more. As they are fairly old they are also well-tested and documented close to perfection. PySide2 is a wrapper for almost all Qt libraries mantained from the same company. When using PySide2 the developer needs to know that there is a lot of underlying C++. This knowledge is particularly useful when handling object finalization. Every PySide2 object created in a Python program really is a wrapper for a C++ object created inside the underlying C++ runtime. Therefore sometimes it can happen that a PySide2 object is valid, but its C++ counterpart has already been deleted. This can happen because a Python object might get deleted when it’s reference count goes to 0. A C++ object is deleted when its destructor is called.
Another characteristic of Qt libraries is that they mediate between logic code and operative systems. Therefore most of the execution time must be given to the library otherwise the user will get a frozen window and/or the library won’t be able to handle user events. This happens through the
.exec_() method of every window object. The developer then needs to execute short tasks on the same thread as the window through an internal event queue. The developer can schedule methods to be called through timers or through slots and signals. Signals are essentially events that do not necessarily have a 1:1 relationship with callbacks (slots in Qt). Meaning that more than a slot can be connected to a signal and vice versa.
If, however, the program needs to execute a long task it is best to do so in a separate thread. Usually such long tasks are network calls or calls with heavy-data returns. This makes a py-algorand-sdk call the perfect use case for threads because they are really just REST calls.
Project File Structure
To really speed up the project it’s useful to design your interface with Qt Creator instead of writing it by hand, which can be a tedious and error-prone process. Qt Creator is simply a way to graphically design your user interface by dragging the widgets where you need them and automating code generation of subclasses, slots and signals. In this project all interfaces are designed this way and exported in a
.ui file which is then compiled into PySide2 code. The code generated is usually a subclass of the base classes of the Qt library which you can then import into your application. The compilation of a
.ui file into PySide2 code is done with
pyside2-uic executable that is included in the package.
Relevant Snippets of Code
Serialization and ChangeContainer
When faced with the problem of saving to persistent memory a collection of information/objects the simple answer is to use json serialization. Python offers two standard solutions: the json package and the pickle package. Neither of them is ideal. The “json” package only deals with native data structures so one would have to write its decoder/encoder for each custom object. The “pickle” package doesn’t serialize in json but rather in a different standard and has known security issues of code injection.
Jsonpickle, on the other hand, is capable of serializing and deserializing custom Python objects in json without the need to write additional methods to the object (at the only cost of some metadata written alongside the actual object).
To reduce the number of times a data structure is written to disk one could implement a wrapper that detects write operations on the structure and write to disk only if a write operation has been issued.
One possible solution (the one in this project) is to convert the data structure to a string, save its hash and then check against it to see if the content is changed. This is very fast to write, although it’s quite an expensive operation and leaves open the possibility that two data structures with different data in them result in the same hash (this is very unlikely).
In order to avoid a very unlikely, but also very frustrating and random, issue of not saving data to the disk the hash solution has been replaced in favor of the more stable and computationally cheap solution below.
We wish to implement functionality to detect changes and to signal if any change occurred. We do this in a base class that we will then inherit from:
class ChangeDetectable: def __init__(self): self._changed = False def save_state(self): self._changed = False def has_changed(self) -> bool: return self._changed
Then we use MutableSequence to implement a list-like object. One could also use MutableMapping for dict-like objects and MutableSet for set-like objects.
The beauty of this approach is that we only need to code behaviour into some very specific “choke points” (abstract methods in MutableSequence). Other methods like append or extend will be defined in terms of those choke points automatically.
class MyList(MutableSequence, ChangeDetectable): def __init__(self): super().__init__() self._list = list() def __getitem__(self, item): return self._list.__getitem__(item) def __setitem__(self, key, value): self._list.__setitem__(key, value) self._changed = True def __delitem__(self, key): self._list.__delitem__(key) self._changed = True def __len__(self): return self._list.__len__() def insert(self, index, item): self._list.insert(index, item) self._changed = True
Most of the time, an interface will have to be loaded with values known only at runtime. When this happens it is tempting to do it at the end of constructor. However, this can lead to problems like when calling a function that can result in an error. As discussed earlier, the library is responsable for showing and keeping the GUI executed. Therefore anything that happens within the constructor is not happening in the GUI event loop.
For instance: the user tries to open the transaction window so the constructor calls through a thread the status of the algod daemon before even showing the window. The call results in an error because the daemon has crashed on the remote machine. The user sees an error message that says “algod daemon not responding” and then, when the constructor is done, the transaction window shows and then closes because of the error.
A possible solution to this is to construct the window and insert into the event queue a timer with 0 countdown (such that it executed as soon as there is time available), pass the execution to the PySide2 library, which will show the window and make it available to the user. Then as soon as there is time available the user interface is filled with the result of a successful call or closed with an error.
py-algorand-sdk and QWorker
As mentioned before it would be best to put all of the network calls inside of a thread and setup a callback for either a success or a failure. Since all calls of the py-algorand-sdk happen through a REST interface those are precisely the calls we want to prevent from interrupting the GUI event loop because they can potentially take a long time if network speeds are poor.
Since there has to be a callback to a slot in the code, it is best to use threads in the Qt library instead of handling the Python thread at the risk of incompatibility with calls and the GUI event loop. PySide2 has multiple solutions that fit different scenarios. However, for a one-time call with light data returning a QWorker fits best.
A QWorker can be overloaded to fit any scenario and it is able to emit custom signals as we can see in this example below:
class AlgorandWorker(QtCore.QRunnable): def __init__(self, fn: callable, *args, **kwargs): super().__init__() self.fn = fn self.args = args self.kwargs = kwargs self.signals = AlgorandWorkerSignals() def run(self): try: result = self.fn(*self.args, **self.kwargs) except Exception as e: self.signals.error.emit(e) else: self.signals.success.emit(result)
In summary, we explored some of the highlights of this project, which aims to provide anyone with the tools to run operations on their own node through kmd. Developing a GUI requires you to be knowledgeable in many fields: design of gui widgets, user expectations, multi-threading and concurrency, network requests, ect. This project exemplifies some technologies fit for these purposes but there are many more out there.
A similar project that could use all or a subset of these skills is, for instance, a blockchain explorer.
Here are some possible future improvements of this project:
- It would be nice in the future to add a persistent refresh in the balance window instead of having to re-open it each time.
- Implement a better function to look for a match between a contact name and the search bar.
- Have all REST calls in a thread, even the ones that don’t take too much time or happen at a point where we are sure that a valid node is up.
I really hope you enjoyed reading about this project and i hope it inspired you to get involved.
You can find the repository of this project here:
I urge everyone to take a look, try the app and email me for any doubt, question or to contribute.