How to build a module for Nano Wallet


#1

How to build a module for Nano Wallet


In this tutorial, you’ll learn how to build a module that allows a user to send XEM from the NanoWallet, easily, in a few lines of JavaScript code. All you need is a basic understanding of this programming language, the rest will be explained, so no worries if you never used AngularJS or ES6.

I) Getting started

Download the source from: https://github.com/NemProject/NanoWallet

Install npm and gulp if you don’t have them

sudo apt-get install npm
npm install -g gulp-cli

Open a console and go to the path of the source folder where your gulpfile and package.json are.

Install all the needed dependencies from package.json:

npm install

When installation is finished, build the app:

gulp

It’ll create a build folder with the functional version in it. We’ll build using gulp every time we change something in the code and want to see the result.

II) Adding a module

All modules have 4 similar files:

  • index.js: Declare your module
  • myModule.config.js: Declare route and other config of your module
  • myModule.controller.js: The module core
  • myModule.html: The module view

You can download a blank module here: https://github.com/QuantumMechanics/NW-Module-Template
It also contains the final module of this tutorial (example folder).

Move the blank module into the modules folder: src/app/modules and rename every myModule occurrences (in file’s name and file’s content) as you wish.

Open src/app/app.js and declare your module with others

import './modules/home';
import './modules/dashboard';
import './modules/signup';
...
import './modules/portal';
import './modules/apostille';
import './modules/myModule';

Then add it in requires

const requires = [
    'ui.router',
    'templates',
    'app.dashboard',
    'app.transferTransaction',
    'app.createMultisig',
    'app.editMultisig',
    ...
    'ngToast',
    'ngStorage',
    'chart.js',
    'pascalprecht.translate',
    'app.lang',
    'app.changelly',
    'app.myModule'
];

Now that your module is added to the app, we need to add access to it in the portal module (it could also be accessed directly from the url you set in the config)

Open src/app/modules/portal/portal.html and add a panel

<div class="col-sm-4">
          <div class="panel-heading row">
            <div class="col-sm-3">
              <i class="fa fa-4x">&#xf2bb; </i>
            </div>
            <div class="col-sm-9">
              <h5>Module name</h5>
            </div>
          </div>
          <div class="panel-body">
            <p>Module presentation</p>
          </div>
          <div class="panelFooter">
            <button class="btn" ui-sref="app.myModule">MyModule</button>
          </div>
</div>

Replace things according to your module, and you can then use gulp to build

gulp

Open Nano located in your build/ folder, log into your wallet, go to services and you’ll see your panel.

If you open your module, it’ll do nothing so let’s send some XEM from there!

III) Writing module core: JavaScript with classes and two-way data binding

Open your controller src/app/modules/myModule/myModule.controller.js

In this controller, you can see a simple class with a constructor.

The constructor is called every time you access your module, so there we want to initialize our properties (or variables) that’ll be used in the controller and the view. We can also use class methods, but we’ll see that later.

Let’s look at its content and start adding things into it. At first, you see that the constructor can take parameters. It is the services that we want to inject. If you want to add a service from our services folder or an angular service like $q, $timeout, etc., you need to call it as a parameter of the constructor and declare it or it can’t be used outside of the constructor.

To send transactions we’ll need the Transactions service, so add it next to the 3 default services in your controller:

    // Set services as constructor parameter
    constructor($location, Alert, Wallet, Transactions) {
          'ngInject';

          // Declare services here
          this._location = $location;
          this._Alert = Alert;
          this._Wallet = Wallet;
          this._Transactions = Transactions;
          
          ....
    }

Note: If you never used this before, you only need to remember that all variables declared with a this can be used anywhere in the controller and the view.

Below services I added a wallet check so nobody can load the module without it

// If no wallet show alert and redirect to home
if (!this._Wallet.current) {
  this._Alert.noWalletLoaded();
  this._location.path('/');
  return;
}

And then we’re going to initialize our properties

 /**
 * Default simple transfer properties
 */
  this.formData = {}
  this.formData.recipient = '';
  this.formData.amount = 0;
  this.formData.message = '';
  this.formData.encryptMessage = false;
  this.formData.fee = 0;

  // To store our password and decrypted/generated private key
  this.common = {
    "password": "",
    "privateKey": ""
  }

  // Multisig data, we won't use it but it is needed anyway
  this.formData.innerFee = 0;
  this.formData.isMultisig = false;
  this.formData.multisigAccount = '';

  // Mosaic data, we won't use it but it is needed anyway
  this.formData.mosaics = null;
  this.mosaicsMetaData = null;

  // To lock our send button if a transaction is not finished processing
  this.okPressed = false;

Now we have everything needed to build the transaction.

Note: For now you’d have to dig the transaction service or transfer transaction module to know that… I’ll work on documentation as soon as possible.

Let’s go to our view, open myModule.html and create a panel with a form to place our data bindings

<div class="col-md-6" style="margin-top:10px">
      <div class="panel panel-default">
      <div class="panel-heading" style="background-color: rgb(68, 68, 68); color: white;border-radius: 0px;">
        <i class="fa fa-chevron-right"></i> Send a simple transfer
      </div>
      <div class="panel-body">
      <label>Password</label>
      <br>
      <input type="password" class="form-control" ng-model="$ctrl.common.password"/>
      <br>
      <label>Recipient</label>
      <br>
      <input type="text" class="form-control" ng-model="$ctrl.formData.recipient"/>
      <br>
      <label>Amount</label>
      <br>
      <input type="number" class="form-control" ng-model="$ctrl.formData.amount" min="0" ng-change="$ctrl.updateFee();"/>
      <br>
      <label>Message</label>
      <br>
      <textarea class="form-control" ng-model="$ctrl.formData.message" rows="4" ng-change="$ctrl.updateFee();"></textarea>
      <br>
      <label>Fee</label>
      <br>
      <span>{{ $ctrl.formData.fee / 1000000 }}</span>
      <br>
      <button ng-click="$ctrl.send()" ng-disabled="$ctrl.okPressed">Send</button>
    </div>
  </div>
</div>

Let’s analyze this, we have:

<input type="password" ng-model="$ctrl.common.password"/>
<input type="text" ng-model="$ctrl.formData.recipient"/>

Simple HTML inputs. We use ng-model to bind a variable to an element. Note that we use $ctrl in the view instead of this.

For the amount and message

<input type="number" ng-model="$ctrl.formData.amount" min="0 ng-change="$ctrl.updateFee();"/>
<textarea class="form-control" ng-model="$ctrl.formData.message" rows="4" ng-change="$ctrl.updateFee();"></textarea>

You can see that we have ng-change, it is used to fire a function on ng-model value changes.

For the fee we use {{ $ctrl.formData.fee / 1000000 }}, curly brackets are needed if the variable is used outside of the angular scope, you can do basic operations on the variables like dividing by 1000000 as I do.

Note: If you look closely you see that ng-change (part of angular) doesn’t need those brackets but a placeholder element will need it. To illustrate:

<input type="text" ng-model="someVar" placeholder="{{ someVar }}" />

Finally we have:

<button ng-click="$ctrl.send()" ng-disabled="$ctrl.okPressed">Send</button>

ng-click is for functions that we want to fire on click, and ng-disabled will lock the button if okPressed is true.

Note: While you can only use one variable into ng-model, you can use as many functions as you want in ng-click and ng-change, for example

ng-click="this.someFunction();this.aBoolean = false;this.anotherFunction();"

Also, you can set conditions into ng-disabled, for example to disable if still processing or if user has not put any password:

ng-disabled="$ctrl.okPressed || !$ctrl.common.password"

Let’s get further now. As you can see, we’ll use updateFee() and send() methods so let’s write those

Open your controller; our methods must be declared after the constructor

/**
 * updateFee() Update transaction fee
 */
  updateFee() {
    let entity = this._Transactions.prepareTransfer(this.common, this.formData, this.mosaicsMetaData);
    this.formData.fee = entity.fee;
  }

Our first method! You can see that there is no parameter into the function, we are using this properties directly as it’s global.

Note: let is almost like var, both are global if not in a block, but var is scoped to the nearest function block and let is scoped to the nearest enclosing block, which can be smaller than a function block.

prepareTransfer from the Transactions service return the entity of our transfer transaction, it contains the fee that interests us. Every time the amount or message will change by user input, the ng-change will trigger updateFee() and we’ll get the actualized fee in our view.

To update the fee at the start of your module you need to call that method into the class constructor.
Below the default module properties you need to add:

this.updateFee();

Now for our last method send(), we need the CryptoHelpers util and for that we are going to use the import feature of ES6:

import CryptoHelpers from '../../utils/CryptoHelpers';

This must be set at the top of your controller, before the class. It is then possible to use it anywhere in the controller.

Note: If you want to use an util in the view, you need to declare it in the constructor like services and properties, for example:

import someUtil from 'path/to/utils/someUtil';
class TransferTransactionCtrl {
  constructor($location, Wallet, Alert, Transactions) {
        'ngInject';
        // Declare services here
        this._location = $location;
        this._Wallet = Wallet;
        this._Alert = Alert;
        this._Transactions = Transactions;
        this._someUtil = someUtil;
        ....
  }
  ...
}

But it is not for our case as CryptoHelpers is only needed in the controller.

IV) Getting user private key and send transactions

CryptoHelpers util contains the functions to decrypt/generate the private key of a wallet account and check it’s validity

/**
 * send() Build and broadcast the transaction to the network
 */
send() {
  // Disable send button;
  this.okPressed = true;

  // Decrypt/generate private key and check it. Returned private key is contained into this.common
  if (!CryptoHelpers.passwordToPrivatekeyClear(this.common, this._Wallet.currentAccount, this._Wallet.algo, true)) {
    this._Alert.invalidPassword();
    // Enable send button
    this.okPressed = false;
    return;
  } else if (!CryptoHelpers.checkAddress(this.common.privateKey, this._Wallet.network, this._Wallet.currentAccount.address)) {
    this._Alert.invalidPassword();
    // Enable send button
    this.okPressed = false;
    return;
  }

  .....
}

The above will use user password to get his private key. Then it’ll check that the returned private key (in this.common) generates the same address as his current account address stored in the wallet service. Otherwise, in the case of brain wallets, it’d generate an invalid private key if the password is wrong.

The second part of the method is building the entity (like in updateFee()) and use the Transactions service to serialize and announce the transaction.

/**
 * send() Build and broadcast the transaction to the network
 */
send() {
  // Disable send button;
  this.okPressed = true;

  // Decrypt/generate private key and check it. Returned private key is contained into this.common
  if (!CryptoHelpers.passwordToPrivatekeyClear(this.common, this._Wallet.currentAccount, this._Wallet.algo, true)) {
    this._Alert.invalidPassword();
    // Enable send button
    this.okPressed = false;
    return;
  } else if (!CryptoHelpers.checkAddress(this.common.privateKey, this._Wallet.network, this._Wallet.currentAccount.address)) {
    this._Alert.invalidPassword();
    // Enable send button
    this.okPressed = false;
    return;
  }

  // Build the entity to serialize
  let entity = this._Transactions.prepareTransfer(this.common, this.formData, this.mosaicsMetaData);

  // Construct transaction byte array, sign and broadcast it to the network
  return this._Transactions.serializeAndAnnounceTransaction(entity, this.common).then((res) => {
    // Check status
    if (res.status === 200) {
      // If code >= 2, it's an error
      if (res.data.code >= 2) {
        this._Alert.transactionError(res.data.message);
      } else {
        this._Alert.transactionSuccess();
      }
    }
    // Enable send button
    this.okPressed = false;
    // Delete private key in common
    this.common.privateKey = '';
  },
  (err) => {
    // Delete private key in common
    this.common.privateKey = '';
    // Enable send button
    this.okPressed = false;
    this._Alert.transactionError('Failed ' + err.data.error + " " + err.data.message);
  });
}

We pass the entity (transaction object) and this.common (containing the private key) in serializeAndAnnounceTransaction, the method of the Transactions service. It’ll serialize the transaction object, sign it and broadcast it to the network.

After it is sent, we receive data in the callback, and we simply analyze it to know the result, then we clean sensitive data.

Now you have both needed methods, that’s it. Use gulp to build and try your module with a testnet wallet, it should send transactions!

Feel free to play with this example, add methods, inject services, import utils and try functions it contains… I am going to work on a detailed documentation to help you.


Apostille implementation questions
Полезные ссылки для разработчиков
#2

lots of people could use this for building on NanoWallet. Thanks :slight_smile:


#3

#4

Wow, this is an absolutely AWESOME tutorial, thanks so much!

I will be using this, together with the source code of the nano wallet to try and build a custom wallet for my project. This will be SO useful. I will probably be back with more questions though at some stage :slight_smile:


#5

Thanks for good tutorial.But I got a error below when I typed “npm install” in command line.

“npm WARN [email protected] No repository field.”

Does anyone know how to solve this?


#6

“npm WARN [email protected] No repository field.”

This suggests that you are not using the NanoWallet repository for npm install but rather that you are trying to install “[email protected]” from another project? Did you add “NanoWallet” to your package.json or what was your procedure?

Simple install procedure is as follows (Linux):

$ git clone https://github.com/NemProject/NanoWallet.git 
$ cd NanoWallet/
$ npm install -g gulp-cli
$ npm install
$ gulp

#7

Problem was missing a first step "git clone…"
That warning has gone.Thanks!!

But next problem has just occurred…

gulp-notify: [Compile Error] Cannot find module ‘./modules/myModule’ from ‘/Users/xxx/Documents/NanoWallet/src/app’

my procedure is same as tutorial(macOS).But this procedure might not be same.[quote=“Quantum_Mechanics, post:1, topic:2976”]
Open src/app/modules/portal/portal.html and add a panel
[/quote]

Where should be added Those text in html file??I couldn’t understand this step’s detail.

Thank you,


#8

Seems you are doing: import './modules/myModule'; in src/app/app.js but there is no myModule folder into src/app/modules.

This step is to add another panel to the portal service so you can access the module from it.

Place the below HTML code into the portal module html file ( https://github.com/NemProject/NanoWallet/blob/master/src/app/modules/portal/portal.html#L151 below highlighted </div> to add the element in the third row of services )

    <div class="col-sm-4">
              <div class="panel-heading row">
                <div class="col-sm-3">
                  <i class="fa fa-4x">&#xf2bb; </i>
                </div>
                <div class="col-sm-9">
                  <h5>Module name</h5>
                </div>
              </div>
              <div class="panel-body">
                <p>Module presentation</p>
              </div>
              <div class="panelFooter">
                <button class="btn" ui-sref="app.myModule">MyModule</button>
              </div>
    </div>

You can also use the url you have set in your module config to access it directly. In case of the myModule example, once logged on Nano Wallet dashboard you can replace /dashboard in url by /my-module


#9

But myModule is in src/app/modules.This is terminal response by “ls” in the directly.
xxxMacBook-Pro:modules xxx$ pwd
/Users/xxx/Documents/NanoWallet/src/app/modules
xxxMacBook-Pro:modules xxx$ ls
account faq multisignature
addressBook home myModule
apostille importanceTransfer namespaces
changelly languages portal
dashboard login signup
explorer mosaics transferTransaction


#10

I just tried: took the myModule folder, placed it into src/app/modules/, opened src/app/app.js, added the module in imports:

import './modules/home';
import './modules/dashboard';
import './modules/signup';
import './modules/login';
...
import './modules/myModule';

And added in requires:

const requires = [
    'ui.router',
    'templates',
    'app.layout',
    ...
    'app.addressBook',
    'app.myModule'
];

Then gulp and it works.

Please try again from a clean install.


#11

Thanks.I’ve just cleaned and started install.
But error is continuing…Uhmmmm

I’ll try other procedure. Thank you.


#12

I am new to blockchain technology.I want to create a network through different nodes
and i can’t find any tutorial to create a network


#13

You want build your own blockchain? It requires development. Should be this public chain or private chain? If private maybe you want use solution like mijin.


#14

good morning. Grahnite
I saw your comment:

Wow, this is an absolutely AWESOME tutorial, thanks so much!

I will be using this, together with the source code of the nano wallet to try and build a custom wallet for my project. This will be SO useful. I will probably be back with more questions though at some stage :slight_smile:

I saw your comment:
can you tell me about your nano wallet project?

I am interested in doing many wallets to transfer XEM.

As to be used to create many coupons with the same discount value; all for example with a discount of one cent of dollar.

What would you recommend me to do?