Our website is made possible by displaying online advertisements to our visitors. Please consider supporting us by disabling your ad blocker.

Create A Cross-Platform Desktop DigiByte DGB Wallet With Angular And Electron

TwitterFacebookRedditLinkedInHacker News

A few weeks back I had written an article titled, Send And Manage DigiByte DGB Coins With Node.js, which had explained how to interact with DigiByte coins via Node.js and the CLI. We saw how to keep track of a wallet, compare against the market value, and send DGB to other wallets. The catch here was that it was all CLI based and not particularly user friendly.

A popular solution towards adding a GUI to a Node.js application comes in the flavor of Electron. With Electron you’re essentially packaging a web application that can interact with native platform APIs.

Since I’m a fan of Angular, we’re going to see how to create a fancy DigiByte wallet using Electron, Node.js, and Angular.

So what are we hoping to accomplish in this particular example? Take a look at the image below which represents our final product.

Electron with Angular DigiByte Wallet

We plan to be able to show our current wallet balance and the value in USD as calculated from the current market value. We also plan to be able to prepare a transaction to be sent to another wallet. However, we won’t actually send the transaction to avoid any accidents in our development process.

This project will rely heavily on the material that we had created in the previous DigiByte with Node.js example.

If at any time you feel you’d like to donate DGB coins, my public address is D9Ms9hnm32q9nceN2b9jNshuZhWcobrmQm. As of right now, you can pick up 100 DGB for less than $10.00.

Create a New Angular Project with the Angular CLI

For simplicity, we’re going to be starting a new Angular project. As previously mentioned, we’re going to be relying heavily on the DigiByte logic we had implemented in the previous tutorial on the subject.

Assuming you have the Angular CLI installed, execute the following:

ng new dgb-wallet

The above command will create a fresh project that uses Webpack at build time. Before we link the project to support Electron, we need to download a few dependencies and configure Bootstrap to make it a little more attractive.

Include the Bootstrap CSS Framework and Project Dependencies

Before we crack open our project, let’s start by downloading the necessary dependencies. We’re going to be using Electron, and the official DigiByte JavaScript library. Both of these dependencies can be downloaded through the following:

npm install digibyte --save
npm install electron --save-dev

With the NPM dependencies installed, let’s configure Bootstrap. We don’t absolutely need it, but since I’m no CSS wizard, it is definitely helpful.

Download Bootstrap and extract the ZIP archive. You’ll want to copy the css, fonts, and js directories into the project’s src/assets directory.

Open the project’s src/index.html file and add the following Markup:

<!doctype html>
<html lang="en">
    <head>
        <meta charset="utf-8">
        <title>DigiByte Wallet</title>
        <base href="/">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <link rel="icon" type="image/x-icon" href="favicon.ico">
        <link rel="stylesheet" href="assets/css/bootstrap.min.css">
    </head>
    <body>
        <app-root></app-root>
    </body>
</html>

Notice that we’ve added the Bootstrap CSS and nothing else. As we progress through this tutorial, we’ll be revisiting this HTML file to make a few more changes. However, notice that we’ve included the Bootstrap CSS as a relative path. It is very important we use relative paths in our project otherwise we’ll get strange results with Electron.

Prepare the Web Application with Electron Support

We’re already installed Electron, but we haven’t configured anything yet. The first thing we want to do is add our driver file for Electron. At the root of your project create an electron.js file with the following:

const {app, BrowserWindow } = require('electron')
const path = require('path')
const url = require('url')

// Keep a global reference of the window object, if you don't, the window will
// be closed automatically when the JavaScript object is garbage collected.
let win

function createWindow () {
    // Create the browser window.
    win = new BrowserWindow({width: 800, height: 650})

    console.log(__dirname);

    // and load the index.html of the app.
    win.loadURL(url.format({
        pathname: path.join(__dirname, 'dist/index.html'),
        protocol: 'file:',
        slashes: true
    }))

    win.setResizable(false);

    // Emitted when the window is closed.
    win.on('closed', () => {
        // Dereference the window object, usually you would store windows
        // in an array if your app supports multi windows, this is the time
        // when you should delete the corresponding element.
        win = null
    })
}

// This method will be called when Electron has finished
// initialization and is ready to create browser windows.
// Some APIs can only be used after this event occurs.
app.on('ready', createWindow)

// Quit when all windows are closed.
app.on('window-all-closed', () => {
    // On macOS it is common for applications and their menu bar
    // to stay active until the user quits explicitly with Cmd + Q
    if (process.platform !== 'darwin') {
        app.quit()
    }
})

app.on('activate', () => {
    // On macOS it's common to re-create a window in the app when the
    // dock icon is clicked and there are no other windows open.
    if (win === null) {
        createWindow()
    }
})


// In this file you can include the rest of your app's specific main process
// code. You can also put them in separate files and require them here.

If you’ve read the Electron Quick Start documentation, the above JavaScript will probably look familiar. Just take note of the following lines:

win.loadURL(url.format({
    pathname: path.join(__dirname, 'dist/index.html'),
    protocol: 'file:',
    slashes: true
}))

Notice that we’re pointing to the dist/index.html file. This means that we’ll be running the built version of our project, not the served version.

Now we need to tell our project that the electron.js file is the file that we want to use. Open the project’s package.json file and include the following line:

{
    // ...
    "main": "electron.js",
    // ...
}

Electron will know what to do based on the content of the main property. While we’re in the package.json file, we should probably add a convenience script since Electron is available locally to our project rather than globally.

Within the package.json file, add the following:

{
    // ...
    "main": "electron.js",
    "scripts": {
        // ...
        "electron": "npm run build; cp electron.js dist/; ./node_modules/.bin/electron ."
    }
    // ...
}

The above electron script will build our Angular project, copy the electron.js file into the dist directory, and then launch with Electron.

We are copying the electron.js file because we will eventually have functions in it that will be called from Angular.

There are two more things that must be done before we can start developing our project. We need to revisit the project’s src/index.html file:

<!doctype html>
<html lang="en">
    <head>
        <meta charset="utf-8">
        <title>DigiByte Wallet</title>
        <base href="./">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <link rel="icon" type="image/x-icon" href="favicon.ico">
        <link rel="stylesheet" href="assets/css/bootstrap.min.css">
    </head>
    <body>
        <app-root></app-root>
        <script>
            var electron = require("electron");
        </script>
    </body>
</html>

There are two differences in the above code. Remember how I mentioned that we need to be using relative paths in our project in order to retain Electron compatibility?. See the following line:

<base href="./">

We’ve added a period character to change it from a root path to a relative path. We’ve also added the following <script> block:

<script>
    var electron = require("electron");
</script>

In a perfect world, we would have been able to import the Electron dependencies from within our JavaScript files, but because of Webpack and how Electron does business, we’re saving ourselves a lot of headache by requiring it in the src/index.html file.

Now we’re ready to start developing our DGB coin wallet!

Add Angular and Node.js Logic for DigiByte DGB Interaction

For simplicity, this particular project is going to use a single component. This means that we won’t need to worry about any routing between views. However, all our heavy lifting is going to happen outside of the single component.

Let’s focus on creating a few things with the Angular CLI:

ng g service DigiByte

The above command will generate a new service where we’ll store most of our DigiByte logic. The component we plan to use already exists within our project.

Before we write Angular code, let’s write a few Node.js functions. Open the project’s electron.js file and include the following:

const {app, BrowserWindow } = require('electron')
const path = require('path')
const url = require('url')
const DigiByte = require("digibyte");

// ...

exports.getAddress = (privateKey) => {
    var result = "";
    if(privateKey && privateKey != "") {
        result = DigiByte.PrivateKey.fromWIF(privateKey).toAddress().toString();
    } else {
        privateKey = new DigiByte.PrivateKey();
        result = privateKey.toAddress();
    }
    return result;
}

exports.createTransaction = (utxos, sourcePrivateKey, sourceAddress, destinationAddress, changeAddress, satoshis) => {
    return new Promise((resolve, reject) => {
        if(utxos.length == 0) {
            reject({ "message": "The source address has no unspent transactions" });
        }
        var transaction = new DigiByte.Transaction();
        for(var i = 0; i < utxos.length; i++) {
            transaction.from(utxos[i]);
        }
        transaction.to(destinationAddress, satoshis);
        transaction.change(changeAddress);
        transaction.sign(sourcePrivateKey);
        resolve(transaction.serialize());
    });
}

In the above code we’ve imported the DigiByte library and created two functions. Do not remove all the already existing Electron code that we had added earlier.

The getAddress function will take a private key and give us an address or it will create a new address for us. This will prevent us from needing to hardcode anything for this example. The createTransaction should look familiar because it was created in our previous example. It takes some sender and recipient information as well as the unspent transaction outputs (UTXOS) and creates a serialized transaction. Remember, we’re not actually going to send the serialized transaction in this example, but we could if we wanted.

You’re probably wondering why we’re not just including these functions in the Angular code. The reason we’re not including these functions in the Angular code is because the browser doesn’t have access to the Node.js Crypto libraries that the DigiByte library depends on. While we could jump through a few hoops to browserify everything, it is easier just to have the Angular code communicate with the Node.js functions.

Let’s dive into some Angular TypeScript code now. Open the project’s src/app/digi-byte.service.ts file and include the following:

import { Injectable } from '@angular/core';
import { Http } from "@angular/http";
import { Observable } from "rxjs/Observable";

declare var electron: any;

@Injectable()
export class DigiByteService {

    private explorerUrl: string;
    private marketUrl: string;
    private mainProcess: any;

    public constructor(private http: Http) {
        this.explorerUrl = "https://digiexplorer.info";
        this.marketUrl = "https://api.coinmarketcap.com/v1/ticker";
        this.mainProcess = electron.remote.require("./electron.js");
    }

    public getMarketValue() {
        return this.http.get(this.marketUrl + "/digibyte/")
            .map(result => result.json())
            .map(result => result[0]);
    }

    public getWalletValue(address) {
        return this.http.get(this.explorerUrl + "/api/addr/" + address)
            .map(result => result.json());
    }

    public getUnspentTransactionOutput(address) {
        return this.http.get(this.explorerUrl + "/api/addr/" + address + "/utxo")
            .map(result => result.json());
    }

    public createTransaction(sourcePrivateKey, sourceAddress, destinationAddress, changeAddress, satoshis) {
        return this.getUnspentTransactionOutput(sourceAddress)
            .switchMap(utxos => Observable.fromPromise(this.mainProcess.createTransaction(utxos, sourcePrivateKey, sourceAddress, destinationAddress, changeAddress, satoshis)))
            .map(result => <string> result);
    }

    public getAddress(privateKey) {
        return this.mainProcess.getAddress(privateKey);
    }

}

While a lot of the above code we’ve seen already in the previous example, how we’re handling it is a little different.

Remember that import we did in the src/index.html file? This line relates to what we’ve done:

declare var electron: any;

Instead of trying to find TypeScript definitions, we’re just going to declare the variable as anything to prevent compiler errors.

The plan is to use remote RESTful APIs as well as the functions that we had created in the Node.js file. To use the functions in the Node.js file, we need to import it, as seen in the constructor method:

this.mainProcess = electron.remote.require("./electron.js");

The releative path above refers to dist/electron.js because remember, we’re copying it at build time. Most of the functions in the DigiByteService class are RxJS versions of what we had previously seen as promises. With the exception of the following:

public createTransaction(sourcePrivateKey, sourceAddress, destinationAddress, changeAddress, satoshis) {
    return this.getUnspentTransactionOutput(sourceAddress)
        .switchMap(utxos => Observable.fromPromise(this.mainProcess.createTransaction(utxos, sourcePrivateKey, sourceAddress, destinationAddress, changeAddress, satoshis)))
        .map(result => <string> result);
}

public getAddress(privateKey) {
    return this.mainProcess.getAddress(privateKey);
}

The createTransaction and getAddress functions above use the mainProcess variable to interact with the Node.js functions. This gives us all the Crypto library features for Angular.

The createTransaction function will get the UTXOS from the RESTful API, and pass it along with the sender and recipient information to the Node.js code.

The DigiByteService class does most of the heavy lifting. Now let’s use this service within our component. Open the project’s src/app/app.component.ts file and include the following:

import { Component, OnInit } from '@angular/core';
import { DigiByteService } from "./digi-byte.service";

@Component({
    selector: 'app-root',
    templateUrl: './app.component.html',
    styleUrls: ['./app.component.css']
})
export class AppComponent implements OnInit {

    public marketValue: any;
    public walletValue: any;
    public input: any;
    public transaction: string;

    public constructor(private digibyte: DigiByteService) {
        this.marketValue = {};
        this.walletValue = {
            balance: "0.00",
            usd: "0.00"
        };
        this.input = {
            privateKey: "",
            recipient: "D9Ms9hnm32q9nceN2b9jNshuZhWcobrmQm",
            changeAddress: "DNRrvQ2nKm87icwgKrP1U1UjNXrdumdDkC",
            amount: 1000
        }
        this.transaction = "";
    }

    public ngOnInit() {}

    public loadWallet(privateKey) {
        this.digibyte.getMarketValue()
            .subscribe(result => {
                this.marketValue = result;
                this.digibyte.getWalletValue(this.digibyte.getAddress(privateKey))
                    .subscribe(result => {
                        this.walletValue.balance = result.balance;
                        this.walletValue.usd = (this.marketValue.price_usd * result.balance).toFixed(2);
                    });
            });
    }

    public createTransaction(privateKey) {
        this.digibyte.createTransaction(privateKey, this.digibyte.getAddress(privateKey), this.input.recipient, this.input.changeAddress, this.input.amount)
            .subscribe(result => {
                this.transaction = result;
            }, error => {
                console.error(error);
            })
    }

}

We’re going to break down each of the functions in our AppComponent class for clarity. Starting with the constructor method, we have the following:

public constructor(private digibyte: DigiByteService) {
    this.marketValue = {};
    this.walletValue = {
        balance: "0.00",
        usd: "0.00"
    };
    this.input = {
        privateKey: "",
        recipient: "D9Ms9hnm32q9nceN2b9jNshuZhWcobrmQm",
        changeAddress: "DNRrvQ2nKm87icwgKrP1U1UjNXrdumdDkC",
        amount: 1000
    }
    this.transaction = "";
}

In the constructor method we injected the service that we had created and initialize a few variables. The marketValue variable will hold information about market prices while the walletValue will hold information about balances that we actually have. The input variable will be bound to a form to collect user input. We’re defaulting a few values for example convenience. Finally, the transaction variable will hold our serialized transaction which we’ll bind to the screen.

This brings us to the loadWallet method:

public loadWallet(privateKey) {
    this.digibyte.getMarketValue()
        .subscribe(result => {
            this.marketValue = result;
            this.digibyte.getWalletValue(this.digibyte.getAddress(privateKey))
                .subscribe(result => {
                    this.walletValue.balance = result.balance;
                    this.walletValue.usd = (this.marketValue.price_usd * result.balance).toFixed(2);
                });
        });
}

Using a passed private key, we can get the address for the key and get the wallet balance. When we compare the wallet balance to the market price, we can get how much our wallet is worth.

The final method, being our createTransaction method, looks like this:

public createTransaction(privateKey) {
    this.digibyte.createTransaction(privateKey, this.digibyte.getAddress(privateKey), this.input.recipient, this.input.changeAddress, this.input.amount)
        .subscribe(result => {
            this.transaction = result;
        }, error => {
            console.error(error);
        })
}

Like with the loadWallet method, the createTransaction method is calling functions from our service. Remember, we’re not doing any heavy lifting in the component, we’re just calling functions from our service.

So what does the HTML that pairs with this TypeScript logic look like? Open the project’s src/app/app.component.html file and include the following Markup:

<div class="container">
    <div class="row">
        <div class="col-md-12">
            <div class="well">
                <h1 class="text-center">{{ walletValue.balance }} DGB - {{ walletValue.usd }} USD</h1>
            </div>
        </div>
    </div>
    <div class="row">
        <div class="col-sm-12">
            <form>
                <div class="form-group">
                    <label for="secretkey">Secret Key</label>
                    <div class="row">
                        <div class="col-sm-10">
                            <input #privateKey type="text" [(ngModel)]="input.privateKey" class="form-control" name="privateKey" placeholder="Secret Key">
                        </div>
                        <div class="col-sm-2">
                            <button type="button" (click)="loadWallet(privateKey.value)" class="btn btn-default btn-block">Load</button>
                        </div>
                    </div>
                </div>
                <div class="form-group">
                    <label for="recipient">Recipient</label>
                    <input type="text" [(ngModel)]="input.recipient" class="form-control" name="recipient" placeholder="Recipient">
                </div>
                <div class="form-group">
                    <label for="changeaddress">Change Address</label>
                    <input type="text" [(ngModel)]="input.changeAddress" class="form-control" name="changeAddress" placeholder="Change Address">
                </div>
                <div class="form-group">
                    <label for="amount">Amount (Satoshi)</label>
                    <input type="text" [(ngModel)]="input.amount" class="form-control" name="amount" placeholder="Amount">
                </div>
                <button type="button" (click)="createTransaction(privateKey.value)" class="btn btn-default">Send</button>
            </form>
        </div>
    </div>
    <div class="row" style="margin-top: 20px">
        <div class="col-sm-12">
            <div class="form-group">
                <textarea [(ngModel)]="transaction" name="transaction" class="form-control" style="height: 120px"></textarea>
            </div>
        </div>
    </div>
</div>

Don’t get overwhelmed by the above HTML. Most of it is Bootstrap framework which is quite heavy to work with when developing. CSS isn’t the core of this example so we won’t think too much on it.

What matters in our HTML is our data binding. Take a look at our private key:

<div class="form-group">
    <label for="secretkey">Secret Key</label>
    <div class="row">
        <div class="col-sm-10">
            <input #privateKey type="text" [(ngModel)]="input.privateKey" class="form-control" name="privateKey" placeholder="Secret Key">
        </div>
        <div class="col-sm-2">
            <button type="button" (click)="loadWallet(privateKey.value)" class="btn btn-default btn-block">Load</button>
        </div>
    </div>
</div>

We’re binding the private key element to the input.privateKey variable found in our TypeScript. However, we also have a local template variable as denoted by the #privateKey attribute. The local variable can be passed to the loadWallet method when we click the button.

The rest of the HTML follows the same patterns.

To wrap things up, the project’s src/app/app.module.ts file needs to be altered to handle the new service and modules used. Open the project’s src/app/app.module.ts file and include the following:

import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { HttpModule } from "@angular/http";
import { FormsModule } from "@angular/forms";
import "rxjs/Rx";

import { AppComponent } from './app.component';
import { DigiByteService } from "./digi-byte.service";

@NgModule({
    declarations: [
        AppComponent
    ],
    imports: [
        BrowserModule,
        HttpModule,
        FormsModule
    ],
    providers: [DigiByteService],
    bootstrap: [AppComponent]
})
export class AppModule { }

In the above TypeScript we’ve imported the HttpModule and FormsModule which allows us to make future HTTP requests and do form binding. We’ve also added the DigiByteService class to the providers array of the @NgModule block.

With a little bit of luck, the project should be ready to be ran. Just make use of the electron script that we added to the package.json file.

Conclusion

You just saw how to create a DigiByte DGB coin wallet using Angular and Electron. This is a step up from our previous example where we had created a CLI based DigiByte wallet. This Electron application is cross-platform and can be used on Windows, Mac, and Linux.

If you’re holding DGB and you feel like donating, my public address is D9Ms9hnm32q9nceN2b9jNshuZhWcobrmQm.

Are you interested in Vue.js or Ripple XRP coins? I did a very similar tutorial titled, Create a Cross-Platform Desktop Ripple XRP Wallet with Vue.js and Electron, which focuses on developing Electron applications with Vue.js instead of Angular. That particular tutorial is based on the Ripple cryptocurrency rather than DigiByte.

Nic Raboy

Nic Raboy

Nic Raboy is an advocate of modern web and mobile development technologies. He has experience in C#, JavaScript, Golang and a variety of frameworks such as Angular, NativeScript, and Unity. Nic writes about his development experiences related to making web and mobile development easier to understand.