Ethereum DAPP (REST API) on AWS Lambda using Node.JS, Web3 Beta and Infura

Aries Camitan
Coinmonks
11 min readJul 25, 2018

--

Tokens is a new way in making online payments and will be the future of Fintech companies.

In this post, I will show how to build simple DAPP that will send tokens to different wallet.

Pre-Requirements

A. Visual Studio Code — https://code.visualstudio.com/

B. Node.JS (v8.10) — https://nodejs.org/en/

C. Metamask — please follow this link on how to install (https://medium.com/@followcoin/how-to-install-metamask-88cbdabc1d28)

D. AWS Account —create AWS account (https://aws.amazon.com/). We need IAM (create API Keys, make sure to use the custom policy based on up document)

E. APEX Up — follow this document (https://up.docs.apex.sh/) to install up command. To know more about using Up command follow this link. (https://medium.freecodecamp.org/up-b3db1ca930ee).

Initiating Project Workspace

Open your Visual Studio Code, then open the folder where you want to create your DAPP. Then, create your folders similar on below image:

Press Ctrl + `(back tick) to show the Terminal Window. Then, run below command to create package.json.

npm init

Then, follow the wizard in initiating your package.json, should see similar below:

Make sure the entry point is app.js. Afterwards, you should see similar below:

{
"name": "web3betainfuralambda",
"version": "1.0.0",
"description": "Sample DAPP to be deployed in Lambda.",
"main": "app.js",
"directories": {
"lib": "lib"
},
"scripts": {
"test": "node ./node_modules/cucumber/bin/cucumber-js",
"start": "node app"
},
"repository": {
"type": "git",
"url": "git+https://github.com/abcamitan/web3BetaInfuraLambda.git"
},
"keywords": [
"Node.JS",
"Web3",
"Lambda",
"Infura",
"Ethereum"
],
"author": "Aries Camitan",
"license": "MIT",
"bugs": {
"url": "https://github.com/abcamitan/web3BetaInfuraLambda/issues"
},
"homepage": "https://github.com/abcamitan/web3BetaInfuraLambda#readme"
}

Create Smart Contract

First, we need to create our contract and deploy to Rinkeby network.

Below is a good tutorial on how to create smart contract and how to deploy to the Ethereum network, from that post I copied the smart contract codes.

Go to https://remix.ethereum.org, then copy and compile below Smart Contract Code.

pragma solidity ^0.4.21; //tells that the source code is written for Solidity version 0.4.21 or anything newer that does not break functionalitycontract aexToken {
// The keyword “public” makes those variables readable from outside.

address public minter;

// Events allow light clients to react on changes efficiently.
mapping (address => uint) public balances;

// This is the constructor whose code is run only when the contract is created
event Sent(address from, address to, uint amount);

constructor() public {

minter = msg.sender;

}

function mint(address receiver, uint amount) public {

if(msg.sender != minter) return;
balances[receiver]+=amount;

}

function send(address receiver, uint amount) public {
if(balances[msg.sender] < amount) return;
balances[msg.sender]-=amount;
balances[receiver]+=amount;
emit Sent(msg.sender, receiver, amount);

}
}

Then, we need to get the output of the compiled contract which is the ABI in json format. To get, click Details button.

Then, look for ABI, then click the Copy button.

Afterwards, paste it inside the abi.json file then save.

Deploy contract to Rinkeby Test Network

I assumed that you already install your Metamask and your Rinkeby wallet has Ethers.

To deploy your compiled Smart Contract, go to Run tab and click Deploy button.

Metamask confirmation window will pop-up. Then, click Confirm button then follow the wizard until it is deployed.

Once transaction confirmed in Metamask, click that transaction.

You will see in etherscan transaction detail that your contract has been created successfully. Now, we need to copy the contract address and keep it somewhere for later use.

Get API Key from Infura

If you do not have account in Infura, do it now by going and registering to https://infura.io/. Afterwards, log-in to your email and verify your Infura account. Then, click LEARN HOW INFURA WORKS button and go to the wizard process or choose to Skip.

Press the OK, LET’S GO button.

Then, click CREATE NEW PROJECT.

Next, give your project a name, then click CREATE PROJECT button.

Afterwards, under ENDPOINT choose RINKEBY. Then, give the contract address you keep previously from Smart contract deployment. Lastly, click the ADD button.

Then, copy the endpoint and keep it somewhere we will use later during the DAPP creation.

Create DAPP Application

Before we start coding, please take note I will not cover any security matter in this post. I let you decide how you should secure your code as well as your server.

To start, we need to run the command below to install all needed node modules.

npm install -save express body-parser cjson web3

Create your main entry point of your application by creating the app.js file. The enter the code below and save.

const express = require('express')
const bodyParser = require('body-parser')
const fs = require('fs')
const path = require('path')
var app = express()// Defining the port number.
// It is important to set to process.env.PORT
// since Lambda will define the PORT explicitly
const PORT = process.env.PORT || 8080
// Supporting every type of body content type
app.use(bodyParser.json())
app.use(bodyParser.urlencoded({ extended: true }))
//Use below codes to automatically add your routing files (endpoints)
var routes = fs.readdirSync(path.join(__dirname, '/route'))
routes.forEach(routesFile => {
if (routesFile.match(/\.js$/)) {
var route = require(path.join(__dirname, '/route/', routesFile))
route(app)
}
})
// show the running port on console
app.listen(PORT, function() {
console.log('server started on port ', PORT)
})

Next, we need to create our routes or endpoints. If haven’t created the route folder, please create it now. Then, inside that folder create token-route.js file and enter the code below and save.

const token = require('../lib/token')
function init(app) {
const path = '/token'

// endpoint to create token to given address
app.post(path+'/mintToken', token.mint)

// endpoint to transfer token from Contract
// there's must be existing token inside the contract to send token
// otherwise, 0 token to send to give address
app.post(path+'/sendToken', token.send)

// check the balance of ether and token from given address
app.get(path+'/getBalance', token.balance)
}
module.exports = init;

Now, we will create the functionality that will call the Smart Contract methods. First, check if the lib folder already exist if not create the folder. Then, inside the lib folder create a new file called token.js file and copy the code below:

const Web3 = require('web3')
const path = require('path')
const cjson = require('cjson')
const TX = require('ethereumjs-tx')
// contract details
const provider = 'https://rinkeby.infura.io/v3/49489982b438407ea553d0ac3dc0671b'
const contractAddress = '0xff8298ae7364d0e79a502dec7d53bc19dfd6ba5f'
const privateKey = new Buffer('01b83f2972df3f5f7894c3425300cd66f4617b0da748dbb25159ccd0d4568455', 'hex')
const defaultAccount = '0x5fe168a1256d574cef13bc5f4e00c021183f4dbe'
const etherscanLink = 'https://rinkeby.etherscan.io/tx/'
// initiate the web3
const web3 = new Web3(provider)
// initiate the contract with null value
var contract = null;
// convert Wei to Eth
function convertWeiToEth( stringValue ) {
if ( typeof stringValue != 'string' ) {
stringValue = String( stringValue );
}
return web3.utils.fromWei( stringValue, 'ether' );
}
// Initiate the Contract
function getContract() {
if (contract === null) {
var abi = cjson.load(path.resolve(__dirname, '../ABI/abi.json'));
var c = new web3.eth.Contract(abi,contractAddress)
contract = c.clone();
}
console.log('Contract Initiated successfully!')
return contract;
}
// send token to Address
async function sendToken(req, res) {
var address = req.body.address
var tokens = Number(req.body.tokens)
if (address && tokens) {
const rawTrans = getContract().methods.send(address, tokens) // contract method
return res.send(await sendSignTransaction(rawTrans))
} else {
res.send({
'message':'Wallet address or no. of tokens is missing.'
})
}
}// Mint/Create token to given address
async function mintToken(req, res) {
var address = req.body.address
var tokens = Number(req.body.tokens)
if (address && tokens) {
const rawTrans = getContract().methods.mint(address, tokens) // contract method
return res.send(await sendSignTransaction(rawTrans))
} else {
res.send({
'message':'Wallet address or no. of tokens is missing.'
})
}
}
// get the balance of given address
async function getBalance(req, res) {
var address = req.query.address
if (address) {
// get the Ether balance of the given address
var ethBalance = convertWeiToEth( await web3.eth.getBalance(address)) || '0'
// get the token balance of the given address
var tokenBalance = await getContract().methods.balances(address).call() || '0'
// response data back to requestor
return res.send({
'Ether Balance': ethBalance,
'Token Balance': tokenBalance
})
}
}
// Send Signed Transaction
async function sendSignTransaction(rawTrans) {
// Initiate values required by the dataTrans
if (rawTrans) {
var txCount = await web3.eth.getTransactionCount(defaultAccount) // needed for nonce
var abiTrans = rawTrans.encodeABI() // encoded contract method

var gas = await rawTrans.estimateGas()
var gasPrice = await web3.eth.getGasPrice()
gasPrice = Number(gasPrice)
gasPrice = gasPrice * 2
var gasLimit = gas * 4
// Initiate the transaction data
var dataTrans = {
nonce: web3.utils.toHex(txCount),
gasLimit: web3.utils.toHex(gasLimit),
gasPrice: web3.utils.toHex(gasPrice),
to: contractAddress,
data: abiTrans
}

// sign transaction
var tx = new TX(dataTrans)
tx.sign(privateKey)
// after signing send the transaction
return await sendSigned(tx)
} else {
throw new console.error('Encoded raw transaction was not given.');
}

}
function sendSigned(tx) {
return new Promise(function(resolve,reject){
// send the signed transaction
web3.eth.sendSignedTransaction('0x' + tx.serialize().toString('hex'))
.once(‘transactionHash’, function(hash){
var result = {
'status':'sent',
'url': etherscanLink + hash,
'message':'click the given url to verify status of transaction'
}
// respond with the result
resolve(result)
})
.then(out => {console.log(out)})
.catch(err => {
// respond with error
reject(err)
})
})
}
module.exports = {
send: sendToken,
mint: mintToken,
balance: getBalance
}

Alright, that’s all we need for our DAPP (Rest API) application. From here I recommend to test your application by running the application using node app command. Then, use API testing tool such as Postman to test your API’s.

Note: If you want to subscribe to Smart Contract events using Infura. You must use websockets instead of normal httpprovider.

Pre-deployment requirements

Before we proceed, make sure you fully understand the setting of AWS credentials and up command from your machine. I assumed you already set required credentials, custom policies and up command is already installed.

First, you need to setup ROLE from AWS under IAM. Create a new role by clicking Create role button.

On the next screen, you need to create a new policy. Click Create Policy button.

Click JSON tab.

Replace the existing value using the JSON below. Then, click Review policy button.

{
"Version": "2012-10-17",
"Statement": [
{
"Action": [
"logs:CreateLogGroup",
"logs:CreateLogStream",
"logs:PutLogEvents",
"ssm:GetParametersByPath",
"ec2:CreateNetworkInterface",
"ec2:DescribeNetworkInterfaces",
"ec2:DeleteNetworkInterface"
],
"Effect": "Allow",
"Resource": "*"
},
{
"Action": [
"logs:CreateLogGroup",
"logs:CreateLogStream",
"logs:PutLogEvents",
"ssm:GetParametersByPath",
"ec2:CreateNetworkInterface",
"ec2:DescribeNetworkInterfaces",
"ec2:DeleteNetworkInterface"
],
"Effect": "Allow",
"Resource": "*"
}
]
}

On the next screen, put a descriptive name and then click the Create Policy button.

Back to the role creation, search the newly created custom policy and then click the tick box. Then, click the Next: Review button.

On the next screen, give a descriptive name and click Create role button.

Afterwards, open the newly created role. Then, click the Trust relationships tab.

Then, modify the existing policy using below texts and click Update Trust Policy button.

{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Service": "apigateway.amazonaws.com"
},
"Action": "sts:AssumeRole"
},
{
"Effect": "Allow",
"Principal": {
"Service": "lambda.amazonaws.com"
},
"Action": "sts:AssumeRole"
}
]
}

And then, copy the ARN link and save it somewhere to be use for up.json configuration file.

Next is create a VPC, subnets (minimum of two subnets) and then attach to Internet Gateway. Please follow this link to create (https://medium.com/@devopslearning/aws-vpc-virtual-private-cloud-d8f8481695b6).

Afterwards, create the up.json file from the root folder. This is the required configuration file to be used with up command. Use the format below:

{
"name":"web3betainfuralambda",
"profile":"lambda_test",
"regions":["us-east-2"],
"lambda": {
"memory": 512,
"timeout": 0,
"role": "<ARN link copied previously>",
"runtime": "nodejs8.10"
},
"vpc": {
"subnets": ["subnet-1c27a366", "subnet-9c25a1e6"],
"security_groups": ["sg-d1c4a4bb"]
}
}

I will not explain what are these attributes. However, what important attributes we need are name, profile, role, runtime and vpc.

name — is the name of the application showing in the lambda dashboard.

profile — is the AWS IAM account that you created with custom policies defined.

role — the role ARN link we copied previously.

runtime — is the node.js version

vpc — under this we need to define the subnet id’s and security group

To know more about the configuration of up click this link (https://up.docs.apex.sh/#configuration)

Deploying to Lambda using up command

Open up your command terminal, then go to your DAPP directory. Then, run below command to your terminal.

up

You should see similar message below,

Then, run below command to get the url of your DAPP application.

up url

Afterwards, test your endpoints using the given URL with Postman or other API testing tool. You can run the up logs -f to see real time console messages from your lambda.

If you received error messages about scrypt like showed below from Lambda console.

You will need to create a new EC2 instance and use the Linux AMI image similar to Lambda instance. The reason for this error is that scrypt binaries is created during npm install is different with the binaries workable with AWS Linux AMI in which Lambda used.

After you’ve created your EC2 instance, below are the necessary steps before you can run npm install on AWS Linux:

  1. Install updates (sudo yum update -y)
  2. Install nvm (curl -o- https://raw.githubusercontent.com/creationix/nvm/v0.33.0/install.sh | bash)
  3. Install node (nvm install 8.10)
  4. Install python 2.7, follow this link (https://myopswork.com/install-python-2-7-10-on-centos-rhel-75f90c5239a5)
  5. Install node-gyp globally (npm install -g node-gyp)
  6. Install development tools (sudo yum groupinstall "Development Tools"). This is needed by scrypt to compile properly.
  7. Install up command. First need to give permission to /user/local/bin (sudo chown -R $(whoami) /usr/local/bin/). Then, install up using the script (sudo curl -sf https://up.apex.sh/install | sh). Afterwards, use the same AWS IAM you’ve created before then setup your AWS credentials.
  8. Upload all your source files that we created above. Note: I recommend to commit and push your source files via Git then download to your newly created EC2 instance.
  9. (Optional) Install git (sudo yum install git). This is needed if you save your repositories via Git (Github, Bitbucket, etc) and you want to clone or download your source codes.

Then, run the npm install from that newly created EC2 instance and run the up command again.

Afterwards, test using Postman or other API endpoint testing tool.

You can refer to my Github repo for this post.

https://github.com/abcamitan/web3BetaInfuraLambda

And that’s all folks. You successfully created your DAPP application and deployed in Lambda.

--

--