Bitzuma

An Introduction to Plugin Development for the Electrum Bitcoin Wallet

By Rich Apodaca | Updated

Electrum was created with extensibility in mind. Not only is the source code released under a liberal open source license, but the wallet itself supports third-party plugins. This article shows how to create a simple plugin, and documents the full plugin API. A previous article covered the Electrum Console API.

Step 1: Download and Run Electrum from Source

Clone the Electrum source repository, check out the release you’ll be using, then install the system dependencies needed to run it. The dependencies you’ll need to install will vary depending on your operating system and past development activities.

After dependencies have been installed, you’ll need to generate the Electrum icons. Do so with:

pyrcc5 icons.qrc -o gui/qt/icons_rc.py

If you’re currently using a wallet on your development system, it may be a good idea to keep it separate. To start Electrum in Testnet mode using a clean wallet:

./electrum -w /path/to/dev/wallet --testnet

After creating the dev wallet, close Electrum.

You may get an error along the lines of “Unexpected magic characters”. The following command will clear it:

find . -name "*.pyc" -delete

Step 2: Create a Plugin Directory

Your plugin will reside inside Electrum’s plugins directory:

mkdir plugins/test
touch plugins/test/__init__.py
touch plugins/test/qt.py

Step 3: Add an Initialization File

Open __init__.py in the plugins/test directory. Give it the following content:

from electrum.i18n import _

fullname = _('Test')            # label displayed under the "Plugins" menu
description = 'This is a test!' # description shown when "?" button pressed
available_for = ['qt']          # available for the Qt interface only

Step 4: Add a UI File

A plugin’s main entry point is the qt.py file. At a minimum, this file must contain a class that inherits from Electrum’s BasePlugin. Add the following to the file at plugins/test/qt.py.

from electrum.plugins import BasePlugin

class Plugin(BasePlugin):

    def __init__(self, parent, config, name):
        BasePlugin.__init__(self, parent, config, name)

Step 5: Test the Plugin

Restarting Electrum should put the Test plugin under the dialog available from Tools>Plugins.

Bare Plugin

Option: Enable Settings

Most Electrum plugins allow end-user configuration through the GUI. Add this capability to your plugin by overriding three methods: requires_settings; settings_widget; and settings_dialog:

from electrum.plugins import BasePlugin
from electrum_gui.qt.util import (EnterButton, Buttons, CloseButton, OkButton, WindowModalDialog)
from PyQt5.QtWidgets import (QVBoxLayout)

class Plugin(BasePlugin):

    def __init__(self, parent, config, name):
        BasePlugin.__init__(self, parent, config, name)

    def requires_settings(self):
        # Return True to add a Settings button.
        return True

    def settings_widget(self, window): 
        # Return a button that when pressed presents a settings dialog.
        return EnterButton(_('Settings'), partial(self.settings_dialog, window))

    def settings_dialog(self, window):
        # Return a settings dialog.
        d = WindowModalDialog(window, _("Email settings"))
        vbox = QVBoxLayout(d)

        d.setMinimumSize(500, 200)
        vbox.addStretch()
        vbox.addLayout(Buttons(CloseButton(d), OkButton(d)))
        d.show()

This plugin will add a Settings button that when pressed produces and empty configuration dialog.

Settings Dialog

Option: Add Hooks

Your plugin can respond to events triggered by the Electrum GUI. The general mechanism for doing so uses the @hook mechanism. Import hook, then implement the hook of interest. All, some, or no hooks may be used.

from electrum.plugins import BasePlugin, hook

class Plugin(BasePlugin):

    def __init__(self, parent, config, name):
        BasePlugin.__init__(self, parent, config, name)

    @hook
    def load_wallet(self, wallet, main_window):
        print("found the wallet", wallet)

Hooks API

create_send_tab

@hook
def create_send_tab(self, grid):
    """ Called after sending a payment

    Args:
        grid: QGridLayout containing the Send tab UI

    """
    pass

set_seed

@hook
def set_seed(self, seed, parent):
    """ Called when the seed dialog is shown
    Args:
        seed: The string representation of the seed
        parent: ButtonsTextEdit containing the seed

    """
    pass

transaction_dialog_update

@hook
def transaction_dialog_update(self, dialog):
    """ Called when the transaction dialog is displayed or updated
    Args:
        dialog: electrum_gui.qt.transaction_dialog.TxDialog representing the transaction

    """
    pass

transaction_dialog

@hook
# transaction dialog shown
def transaction_dialog(self, dialog):
    """ Called when the transaction dialog is displayed

    Args:
        dialog: electrum_gui.qt.transaction_dialog.TxDialog representing the transaction

    """
    pass

This dialog can be decorated as follows:

@hook
def transaction_dialog(self, dialog):
    button = QPushButton(_("Test Button"))

    button.clicked.connect(lambda: print("button clicked..."))
    dialog.buttons.insert(0, button)
    button.show()

This modification places a button labeled “Test Button” onto the Transaction detail dialog.

Test Button

password_dialog

@hook
def password_dialog(self, edit, parent, position):
    """ Called when the user is prompted to enter a wallet password

    Args:
        edit PyQt5.QtWidgets.QLineEdit
        parent PyQt5.QtWidgets.QGridLayout
        position

    """
    pass

init_qt

@hook
def init_qt(self, gui):
    """ Called after plugin is activated by ticking box, or at startup if already active.

    Args:
        gui: electrum_gui.qt.ElectrumGui

    """
    pass

load_wallet

@hook
def load_wallet(self, wallet, window):
    """ Called after wallet is loaded

    Args:
        wallet:
        window: electrum_gui.qt.main_window.ElectrumWindow

    """
    pass

close_wallet

@hook
# wallet closed
def close_wallet(self, wallet):
    """ Called when a wallet is closed.

    Args:
        wallet: the wallet that was closed

    """
    pass

receive_list_menu

@hook
def receive_list_menu(self, menu, address):
    """ Called after right-clicking on a receive payment request

    Args:
      menu: PyQt5.QtWidgets.QMenu
      address: string address for the request

    """
    pass

on_new_window

@hook
def on_new_window(self, window):
    """ Called after a new main window is created.

    Args:
        window: electrum_gui.qt.main_window.ElectrumWindow

    """
    pass

on_close_window

@hook
def on_close_window(self, window):
    """ Called after the main window closes.

    Args:
        window: electrum_gui.qt.main_window.ElectrumWindow

    """
    pass

show_text_edit

@hook
def show_text_edit(self, edit):
    """ Called when an xpub or address private key is shown

    Args:
        edit: electrum_gui.qt.qrtextedit.ShowQRTextEdit

    """
    pass

init_menubar_tools

@hook
def init_menubar_tools(self, window, menu):
    """ Called when the Tools menu is initialized

    Args:
        window: electrum_gui.qt.main_window.ElectrumWindow
        menu: PyQt5.QtWidgets.QMenu

    """
    pass

This hook can be used to add an item to the Tools menu:

@hook
def init_menubar_tools(self, window, menu):
    menu.addSeparator()
    menu.addAction(_("&Testing"), lambda: print("item selected..."))

Doing so updates the Tools menu accordingly:

Tools Menu

abort_send

@hook
def abort_send(self, window):
    """ Called when the abort dialog is displayed prior to broadcasting a transaction.

    Args:
        window: electrum_gui.qt.main_window.ElectrumWindow

    """
    pass

scan_text_edit

@hook
def scan_text_edit(self, parent):
    """ Called a camera scans a QR code.

    Args:
        parent:

    """
    pass

receive_menu

def receive_menu(self, menu, addresses, wallet):
    """ Called when one or more addresses are right-clicked from the Addresses tab

    Args:
      menu: 
      addresses: an array containing the selected addresses
      wallet: PyQt5.QtWidgets.QMenu

    """
    pass

create_history_tab

def create_history_tab(self):
    """ Unknown functionality.

    """
    pass