DApp is a web application that will interact with smart contracts deployed on the blockchain. In this article, I will walk you through the steps for creating your first DApp using Angular and Ethereum. The integration process should be similar for the other UI frameworks like Ext JS and React.js. This article is the part of the series of articles on DApps for enabling you to write a most effective distributed applications.

Prerequisite

Ensure that the following is already installed and working on your machine:

npm

truffle

ganache

node

ng

solidity (solc)

Also, this article assumes that you do have some understanding of Angular application development. Also, while this article assumes that you may not have much idea about solidity, however, the solidity smart contract piece is very small and it should not overwhelm you.

Creating A Smart Contract

In this article, we will use the Ethereum Development tool, Truffle, for creating and managing the smart contract. We will be creating a very simple payment contract, which will do the following:

it will allow the user to transfer fund from his/her account to an account on the network

it will allow the user to query his/her balance

Create an empty project directory and use the truffle init command to create a bare Truffle project with no smart contracts included in it.

Creating a bare project $ truffle init Downloading... Unpacking... Setting up... Unbox successful. Sweet! Commands: Compile: truffle compile Migrate: truffle migrate Test contracts: truffle test 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 $ truffle init Downloading . . . Unpacking . . . Setting up . . . Unbox successful . Sweet ! Commands : Compile : truffle compile Migrate : truffle migrate Test contracts : truffle test

Above steps result in a project created with the following file structure and the bare minimum content:

You may like to note the following:

Migrations are Javascript files that help you deploy contracts to the Ethereum network. These files are responsible for staging your deployment tasks, and they’re written under the assumption that your deployment needs will change over time. As your project evolves, you’ll create new migration scripts to further this evolution on the blockchain.

1_initial_migration.js is the sequenced migration file and during the migration, all these migrations get executed in the given sequence

truffle.js allows you to configure project details, specifically the networks on which the project will be deployed. the truffle-config.js is sometimes used on the windows machine prefer using the power shell and hence delete the truffle-config.js from your project

in the test directory, you shall be creating test automation suits

Add Payment Contract

Add a payment contract by adding the Payment.sol file in the contract folder of your project.

Payment.sol pragma solidity ^0.4.17; contract Payment { address transferFrom; address transferTo; uint paymentAmount; constructor() public { transferFrom = msg.sender; } event TransferFund(address _transferTo, address _transferFrom, uint amount); function transferFund(address _transferTo) public payable returns (bool){ transferTo = _transferTo; transferTo.transfer(msg.value); emit TransferFund(transferTo, transferFrom, msg.value); return true; } function getBalanceOfCurrentAccount() public payable returns (uint) { return transferFrom.balance; } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 pragma solidity ^ 0.4.17 ; contract Payment { address transferFrom ; address transferTo ; uint paymentAmount ; constructor ( ) public { transferFrom = msg . sender ; } event TransferFund ( address _transferTo , address _transferFrom , uint amount ) ; function transferFund ( address _transferTo ) public payable returns ( bool ) { transferTo = _transferTo ; transferTo . transfer ( msg . value ) ; emit TransferFund ( transferTo , transferFrom , msg . value ) ; return true ; } function getBalanceOfCurrentAccount ( ) public payable returns ( uint ) { return transferFrom . balance ; } }

Note

The transferFund function must be marked payable to ensure that any ether passed to this function get accepted

The TransferFund event allows logging of the event and the interested javascript client can watch for this event and take desired actions

Configure Deployment Network

Assuming that you have Ganache up and running on your machine, configure your truffle.js with the following details:

Custom network configuration module.exports = { networks : { ganache : { host : 'localhost', port : 7545, // By default Ganache runs on this port. network_id : "*" // network_id for ganache is 5777. However, by keeping * as value you can run this node on any network } } }; 1 2 3 4 5 6 7 8 9 module . exports = { networks : { ganache : { host : 'localhost' , port : 7545 , // By default Ganache runs on this port. network_id : "*" // network_id for ganache is 5777. However, by keeping * as value you can run this node on any network } } } ;

Run the contract deployment command, as shown below:

Contract Deployment $ truffle migrate --compile-all --reset --network ganache Compiling ./contracts/Migrations.sol... Compiling ./contracts/Payment.sol... Writing artifacts to ./build/contracts Using network 'ganache'. Running migration: 1_initial_migration.js Replacing Migrations... ... 0x8f8e457f2584b9d6a90d17a02f4547a56127e20d4f10cc32f951a86dc5db4e5b Migrations: 0xf8988a2e0d1c41ddef64374441f4169342105df5 Saving successful migration to network... ... 0xbccbd28c27eb973be48c47656f50f7a0cc696f8ce93e47efab28af09b216c970 Saving artifacts... Running migration: 2_deploy_contracts.js Replacing Payment... ... 0x99f9eabc15a3688c78689e084d521e5dbe7dc4531b2084464d332fec4f2c7b14 Payment: 0x028b63d210d8228e33c21bce8d87007fe7848e8d Saving successful migration to network... ... 0xb217f30cf4fb40b837f5340c565aacfc80dbcc9e75e9ed42fd5be3b51ffe10c2 Saving artifacts... 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 $ truffle migrate -- compile - all -- reset -- network ganache Compiling . / contracts / Migrations . sol . . . Compiling . / contracts / Payment . sol . . . Writing artifacts to . / build / contracts Using network 'ganache' . Running migration : 1_initial_migration.js Replacing Migrations . . . . . . 0x8f8e457f2584b9d6a90d17a02f4547a56127e20d4f10cc32f951a86dc5db4e5b Migrations : 0xf8988a2e0d1c41ddef64374441f4169342105df5 Saving successful migration to network . . . . . . 0xbccbd28c27eb973be48c47656f50f7a0cc696f8ce93e47efab28af09b216c970 Saving artifacts . . . Running migration : 2_deploy_contracts.js Replacing Payment . . . . . . 0x99f9eabc15a3688c78689e084d521e5dbe7dc4531b2084464d332fec4f2c7b14 Payment : 0x028b63d210d8228e33c21bce8d87007fe7848e8d Saving successful migration to network . . . . . . 0xb217f30cf4fb40b837f5340c565aacfc80dbcc9e75e9ed42fd5be3b51ffe10c2 Saving artifacts . . .

Verifying your contract in the Truffle Console

Open truffle console and perform a transfer between two accounts. Also, check balances of these accounts before and after the transfer.

Payment Transfer Aloks-MacBook-Pro-2:payment alokranjan$ truffle console --network ganache truffle(ganache)> Payment.deployed().then(function(instance){app = instance;}) undefined truffle(ganache)> web3.fromWei(web3.eth.getBalance(web3.eth.accounts[0]), "ether").toNumber() 89.92725709999996 truffle(ganache)> web3.fromWei(web3.eth.getBalance(web3.eth.accounts[2]), "ether").toNumber() 99.97754 truffle(ganache)> app.transferFund(web3.eth.accounts[2], {from:web3.eth.accounts[0], value:web3.toWei("6.5", "ether")}) { tx: '0xe3fa87474e52ab989e32714de74dd874bb46395bf98d8b1ca8e4061c346ffa06', receipt: { transactionHash: '0xe3fa87474e52ab989e32714de74dd874bb46395bf98d8b1ca8e4061c346ffa06', transactionIndex: 0, blockHash: '0x8d85859241e2dd69a7d10ec7d38248e7cb32f7669bd3fa47029183c51b9d3422', blockNumber: 44, gasUsed: 50943, cumulativeGasUsed: 50943, contractAddress: null, logs: [], status: '0x01', logsBloom: '0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000' }, logs: [] } truffle(ganache)> web3.fromWei(web3.eth.getBalance(web3.eth.accounts[0]), "ether").toNumber() 83.42216279999995 truffle(ganache)> web3.fromWei(web3.eth.getBalance(web3.eth.accounts[2]), "ether").toNumber() 106.47754 truffle(ganache)> 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 Aloks - MacBook - Pro - 2 : payment alokranjan $ truffle console -- network ganache truffle ( ganache ) > Payment . deployed ( ) . then ( function ( instance ) { app = instance ; } ) undefined truffle ( ganache ) > web3 . fromWei ( web3 . eth . getBalance ( web3 . eth . accounts [ 0 ] ) , "ether" ) . toNumber ( ) 89.92725709999996 truffle ( ganache ) > web3 . fromWei ( web3 . eth . getBalance ( web3 . eth . accounts [ 2 ] ) , "ether" ) . toNumber ( ) 99.97754 truffle ( ganache ) > app . transferFund ( web3 . eth . accounts [ 2 ] , { from : web3 . eth . accounts [ 0 ] , value : web3 . toWei ( "6.5" , "ether" ) } ) { tx : '0xe3fa87474e52ab989e32714de74dd874bb46395bf98d8b1ca8e4061c346ffa06' , receipt : { transactionHash : '0xe3fa87474e52ab989e32714de74dd874bb46395bf98d8b1ca8e4061c346ffa06' , transactionIndex : 0 , blockHash : '0x8d85859241e2dd69a7d10ec7d38248e7cb32f7669bd3fa47029183c51b9d3422' , blockNumber : 44 , gasUsed : 50943 , cumulativeGasUsed : 50943 , contractAddress : null , logs : [ ] , status : '0x01' , logsBloom : '0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000' } , logs : [ ] } truffle ( ganache ) > web3 . fromWei ( web3 . eth . getBalance ( web3 . eth . accounts [ 0 ] ) , "ether" ) . toNumber ( ) 83.42216279999995 truffle ( ganache ) > web3 . fromWei ( web3 . eth . getBalance ( web3 . eth . accounts [ 2 ] ) , "ether" ) . toNumber ( ) 106.47754 truffle ( ganache ) >

When you check Ganache, you will see the following transaction corresponding to the above transfer:

Creating an Angular App

Follow the instruction on the below URL and create the initial default App: https://angular.io/tutorial/toh-pt0

Use the ng new command to create a fund transfer app and use ng serve to start the application:

New Angular App ng new transfer cd transfer/ ng serve --open 1 2 3 4 5 ng new transfer cd transfer / ng serve -- open

Check out the code from the repository https://github.com/abhilashahyd/ethdapp/tree/master in a separate folder and review the codes in the following files:

Copy the contents in such a way that your DApp resemble the following screen:

Linking with the Ethereum Network

web3.js

web3.js is a collection of libraries which allow you to interact with a local or remote ethereum node, using an HTTP or IPC connection.

Installing web3.js

$ cd transfer/ $ npm install web3@0.20.5 1 2 3 $ cd transfer / $ npm install web3 @ 0.20.5

The latest version of the web3 may not be compatible with Angular 6. Hence, if you face any challenge then try installing a stable version of web3.

Truffle Contract

The truffle-contract provides contract abstraction. The contract abstractions are wrapper code that makes interaction with your contract easy.

Install truffle-contract and generate service using the following commands:

$ npm install truffle-contract $ ng generate service ethcontract CREATE src/app/ethcontract.service.spec.ts (404 bytes) CREATE src/app/ethcontract.service.ts (140 bytes) 1 2 3 4 5 $ npm install truffle - contract $ ng generate service ethcontract CREATE src / app / ethcontract . service . spec . ts ( 404 bytes ) CREATE src / app / ethcontract . service . ts ( 140 bytes )

Push all the code responsible for interaction with the network or contract into the ethcontract service. For this demo application, we will need the following

constructor for primarily initializing the web3Provider

getAccountInfo – to retrieve the account address (which is responsible for paying the gas and transferring the ether) and the balance of that account

transferEther – for making the actual transferFund contract call

The content of the service file would look as shown below:

ethcontract.service.ts import { Injectable } from '@angular/core'; import * as Web3 from 'web3'; import * as TruffleContract from 'truffle-contract'; declare let require: any; declare let window: any; let tokenAbi = require('../../../build/contracts/Payment.json'); @Injectable({ providedIn: 'root' }) export class EthcontractService { private web3Provider: null, private contracts: {}, constructor() { if (typeof window.web3 !== 'undefined') { this.web3Provider = window.web3.currentProvider; } else { this.web3Provider = new Web3.providers.HttpProvider('http://localhost:7545'); } window.web3 = new Web3(this.web3Provider); } getAccountInfo() { return new Promise((resolve, reject) => { window.web3.eth.getCoinbase(function(err, account) { if(err === null) { web3.eth.getBalance(account, function(err, balance) { if(err === null) { return resolve({fromAccount: account, balance:web3.fromWei(balance, "ether")}); } else { return reject("error!"); } }); } }); }); } transferEther( _transferFrom, _transferTo, _amount, _remarks ) { let that = this; return new Promise((resolve, reject) => { let paymentContract = TruffleContract(tokenAbi); paymentContract.setProvider(that.web3Provider); paymentContract.deployed().then(function(instance) { return instance.transferFund( _transferTo, { from:_transferFrom, value:web3.toWei(_amount, "ether") }); }).then(function(status) { if(status) { return resolve({status:true}); } }).catch(function(error){ console.log(error); return reject("Error in transferEther service call"); }); }); } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 import { Injectable } from '@angular/core' ; import * as Web3 from 'web3' ; import * as TruffleContract from 'truffle-contract' ; declare let require : any ; declare let window : any ; let tokenAbi = require ( '../../../build/contracts/Payment.json' ) ; @ Injectable ( { providedIn : 'root' } ) export class EthcontractService { private web3Provider : null , private contracts : { } , constructor ( ) { if ( typeof window . web3 !== 'undefined' ) { this . web3Provider = window . web3 . currentProvider ; } else { this . web3Provider = new Web3 . providers . HttpProvider ( 'http://localhost:7545' ) ; } window . web3 = new Web3 ( this . web3Provider ) ; } getAccountInfo ( ) { return new Promise ( ( resolve , reject ) = > { window . web3 . eth . getCoinbase ( function ( err , account ) { if ( err === null ) { web3 . eth . getBalance ( account , function ( err , balance ) { if ( err === null ) { return resolve ( { fromAccount : account , balance : web3 . fromWei ( balance , "ether" ) } ) ; } else { return reject ( "error!" ) ; } } ) ; } } ) ; } ) ; } transferEther ( _transferFrom , _transferTo , _amount , _remarks ) { let that = this ; return new Promise ( ( resolve , reject ) = > { let paymentContract = TruffleContract ( tokenAbi ) ; paymentContract . setProvider ( that . web3Provider ) ; paymentContract . deployed ( ) . then ( function ( instance ) { return instance . transferFund ( _transferTo , { from : _transferFrom , value : web3 . toWei ( _amount , "ether" ) } ) ; } ) . then ( function ( status ) { if ( status ) { return resolve ( { status : true } ) ; } } ) . catch ( function ( error ) { console . log ( error ) ; return reject ( "Error in transferEther service call" ) ; } ) ; } ) ; } }

Import the EthcontractService in app.component.ts. Implement initAndDisplayAccount method and transferEther event handler.

The code should look like the one as shown below:

app.component.ts import { Component } from '@angular/core'; import { EthcontractService } from './ethcontract.service'; @Component({ selector: 'app-root', templateUrl: './app.component.html', styleUrls: ['./app.component.css'] }) export class AppComponent implements ngOnInit { title = 'your first DApp in Angular'; accounts:any; transferFrom = '0x0'; balance ='0 ETH'; transferTo=''; amount=0; remarks=''; constructor( private ethcontractService: EthcontractService ){ this.initAndDisplayAccount(); } initAndDisplayAccount = () => { let that = this; this.ethcontractService.getAccountInfo().then(function(acctInfo){ that.transferFrom = acctInfo.fromAccount; that.balance = acctInfo.balance; }).catch(function(error){ console.log(error); }); }; transferEther(event){ let that = this; this.ethcontractService.transferEther( this.transferFrom, this.transferTo, this.amount, this.remarks ).then(function(){ that.initAndDisplayAccount(); }).catch(function(error){ console.log(error); that.initAndDisplayAccount(); }); } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 import { Component } from '@angular/core' ; import { EthcontractService } from './ethcontract.service' ; @ Component ( { selector : 'app-root' , templateUrl : './app.component.html' , styleUrls : [ './app.component.css' ] } ) export class AppComponent implements ngOnInit { title = 'your first DApp in Angular' ; accounts : any ; transferFrom = '0x0' ; balance = '0 ETH' ; transferTo = '' ; amount = 0 ; remarks = '' ; constructor ( private ethcontractService : EthcontractService ) { this . initAndDisplayAccount ( ) ; } initAndDisplayAccount = ( ) = > { let that = this ; this . ethcontractService . getAccountInfo ( ) . then ( function ( acctInfo ) { that . transferFrom = acctInfo . fromAccount ; that . balance = acctInfo . balance ; } ) . catch ( function ( error ) { console . log ( error ) ; } ) ; } ; transferEther ( event ) { let that = this ; this . ethcontractService . transferEther ( this . transferFrom , this . transferTo , this . amount , this . remarks ) . then ( function ( ) { that . initAndDisplayAccount ( ) ; } ) . catch ( function ( error ) { console . log ( error ) ; that . initAndDisplayAccount ( ) ; } ) ; } }

Add EthcontractService as providers in app.module.ts. The code should look like the one shown below:

app.module.ts import { BrowserModule } from '@angular/platform-browser'; import { NgModule } from '@angular/core'; import { FormsModule } from '@angular/forms'; import { AppComponent } from './app.component'; import { EthcontractService } from './ethcontract.service'; @NgModule({ declarations: [ AppComponent ], imports: [ BrowserModule, FormsModule ], providers: [EthcontractService], bootstrap: [AppComponent] }) export class AppModule { } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 import { BrowserModule } from '@angular/platform-browser' ; import { NgModule } from '@angular/core' ; import { FormsModule } from '@angular/forms' ; import { AppComponent } from './app.component' ; import { EthcontractService } from './ethcontract.service' ; @ NgModule ( { declarations : [ AppComponent ] , imports : [ BrowserModule , FormsModule ] , providers : [ EthcontractService ] , bootstrap : [ AppComponent ] } ) export class AppModule { }

Ideally, this should be enough and when you launch the application, you shall get something like this:

Note that the address is the address of the first account in the Ganache, which gets used as coinbase. When the app gets initialized, the balance of the From account is also retrieved. After you transfer some ether, the balance of the coinbase account decreases by the equivalent amount as shown in below screen:

The transaction can be verified by checking the transaction lists in the ganache interface:

The following screen shows the changes in the balance of two accounts:

Congratulations! You just developed and verified a distributed app built using Ethereum and Angular. I hope this encourages you to build your own apps.

In this article, I have used Ganache, a personal Ethereum blockchain, to test the application. By default, in Ganache, the blocks in the blockchain is getting created and mined as soon as the transaction is getting created and hence you were abstracted from the complexity of mining the transactions. This article doesn’t talk about various aspects involved in eventually taking your DApps to the public network. In next few articles, I will guide you in that direction.

Summary

At the very high level, a DApp is like any other web/mobile application. However, instead of interacting with a central server, it interacts with a blockchain network. While I have used Angular as a UI framework, depending on the business need, you can use other frameworks like Ext JS, React.js, etc. to build DApps. Similarly, in this article I have used Ethereum, however, you can choose to use other blockchain alternatives -e.g. Hyperledger Fabric/Sawtooth. We strongly believe that AI and Blockchain will eventually drive the digital experience. We hope to see you leveraging these wonderful technologies!

References