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.
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.
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:
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:
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.
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:
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
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:
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:
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:
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:
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
Hi Alok Ranjan,
Thanking you for sharing. But I got an error as below when i ran the Dapp:
core.js:3077 Angular is running in the development mode. Call enableProdMode() to enable the production mode.
app.component.ts:33 0x8781ec47991524c53020e3f0d1fefdd5151c23cb
app.component.ts:34 BigNumber
app.component.ts:42 debug-111111111111111111111111111
ethcontract.service.ts:55 0x8781ec47991524c53020e3f0d1fefdd5151c23cb
ethcontract.service.ts:56 0x4240588e2bcEAF49FfC0c20f7B3E7B5d6149FEA8
ethcontract.service.ts:57 2
ethcontract.service.ts:58 sda
app.component.ts:62 debug-555555555555555555555
ethcontract.service.ts:64 debug-222222222222222222222222
core.js:1633 ERROR ReferenceError: Buffer is not defined
at Object.hexOrBuffer (index.js:39)
at Object.decodeEvent (index.js:123)
at contract.js:74
at Array.map ()
at Object.decodeLogs (contract.js:45)
at Object.callback (contract.js:187)
at method.js:142
at requestmanager.js:89
at XMLHttpRequest.request.onreadystatechange [as __zone_symbol__ON_PROPERTYreadystatechange] (httpprovider.js:128)
Did you have the same error:
core.js:1633 ERROR ReferenceError: Buffer is not defined
OS: windows 7
node: 8.11.3
ng: 6.0.8
npm: 6.2.0
I appreciate your support.
Emerson
What is the cli version you are using?
I haven’t faced this problem, but as a workaround you can try this solution given @ https://stackoverflow.com/questions/50356408/upgrading-to-angular-6-x-gives-uncaught-referenceerror-global-is-not-defined/50356546
Add following code in your index.html
var global = global || window;
var Buffer = Buffer || [];
var process = process || {
env: { DEBUG: undefined },
version: []
};
Hello Alok
Thanks for the tutorial, when running ng serve –open.
ERROR in src/app/ethcontract.service.ts(15,29): error TS1005: ‘;’ expected.
src/app/ethcontract.service.ts(16,24): error TS1005: ‘;’ expected.
Ubuntu Ubuntu 18.04.1 LTS
nmp 6.4.1
node v9.11.1
ng + @angular/cli@6.2.3
Grateful for the help.
Hello Alfredo,
Please follow the below steps and retry:
1. Install latest typescript version using
npm install -g typescript
2. Clone the latest code from git and also compile the contracts(step added in Readme).
3. Also make sure that the port number is same as in ganache settings.
Let me know if you still face any errors.
Hello Alok.
Thank you for a great post.
But I’m having a problem in beginning.
When I get to truffle console, I ran the command
web3.fromWei(web3.eth.getBalance(web3.eth.accounts[0]), “ether”).toNumber()
to check if it works correctly or not and I get an error as followings.
I get an error like this
/usr/local/lib/node_modules/truffle/build/cli.bundled.js:372384
throw new Error(‘Provided address “‘+ address +'” is invalid, the capitalization checksum test failed, or its an indrect IBAN address which can\’t be converted.’);
^
Error: Provided address “undefined” is invalid, the capitalization checksum test failed, or its an indrect IBAN address which can’t be converted.
at Method.inputAddressFormatter (/usr/local/lib/node_modules/truffle/build/cli.bundled.js:372384:11)
at /usr/local/lib/node_modules/truffle/build/cli.bundled.js:81614:38
at Array.map ()
at Method.formatInput (/usr/local/lib/node_modules/truffle/build/cli.bundled.js:81612:32)
at Method.toPayload (/usr/local/lib/node_modules/truffle/build/cli.bundled.js:81647:23)
at Eth.send [as getBalance] (/usr/local/lib/node_modules/truffle/build/cli.bundled.js:81937:30)
at evalmachine.:0:23
at Script.runInContext (vm.js:107:20)
at runScript (/usr/local/lib/node_modules/truffle/build/cli.bundled.js:458795:14)
at Console.interpret (/usr/local/lib/node_modules/truffle/build/cli.bundled.js:458809:19)
at ReplManager.interpret (/usr/local/lib/node_modules/truffle/build/cli.bundled.js:459735:18)
at bound (domain.js:395:14)
at REPLServer.runBound [as eval] (domain.js:408:12)
at REPLServer.onLine (repl.js:639:10)
at REPLServer.emit (events.js:182:13)
at REPLServer.EventEmitter.emit (domain.js:441:20)
at REPLServer.Interface._onLine (readline.js:290:10)
at REPLServer.Interface._line (readline.js:638:8)
at REPLServer.Interface._ttyWrite (readline.js:919:14)
at REPLServer.self._ttyWrite (repl.js:712:7)
at ReadStream.onkeypress (readline.js:168:10)
at ReadStream.emit (events.js:182:13)
I did exactly as what you have taught but I get stuck there.
Please help.
Thank you in advance.
Hi,
I am a new to Angular and Ethereum . I have been following your post however i am unable to compile the contract as it throws the following error:
TypeError: Member “transfer” not found or not visible after argument-dependent lookup in address.
transferTo.transfer(msg.value);
Please guide me so as to what i am missing. where is the transfer function referencing too?
I keed getting “invalid address” error. But only if I have “MetaMask” installed. If i remove it, the error doesn’t appear. But to make this app work we need MetaMask. Can you help please?
Maybe you need to allow the connection in Metamask. For instance, if you are using localhost then in Metamask go to configuration, connections an add site: localhost:4200
It worked for me, best regards!
I am getting below error.
Could not connect to your Ethereum client with the following parameters:
– host > 127.0.0.1
– port > 7545
– network_id > 5777
Please check that your Ethereum client:
– is running
– is accepting RPC connections (i.e., “–rpc” or “–http” option is used in geth)
– is accessible over the network
– is properly configured in your Truffle configuration file (truffle-config.js)
Can you please help me out.
Run the following comment before your console start
ganache-cli -p 8545