Private Blockchains: Hyperledger Composer Javascript API

Published Mar 28, 2018Last updated Apr 23, 2018

Introduction

In my last article, I gave a quick overview of the Hyperledger Composer framework to build a business network with a private blockchain technology. I used a land registry network to show how the framework works. We then used a React application to use the REST API provided.

This time, instead of using the REST API, I made a little command line application using the Javascript API. The concept is simple. You enter commands in your terminal to trigger actions ( retrieve data, create assets and/or transactions ). We will re-use the same land registry network I used in the previous article.

Connecting to the composer runtime

First, we need to have our private blockchain running. If you haven't gone through my last article to set up your environment, you need to do it right now.

If you went through the article, you need to run a few commands to launch the runtime:

  • First, you need to launch the ./startFabric.sh command from the folder I called fabric-tools in the last article.

  • Next, from the land-registry folder, you need to install the composer runtime: composer runtime install --card PeerAdmin@hlfv1 --businessNetworkName land-registry

  • Finally, still from the land-registry folder, deploy the business network: composer network start --card PeerAdmin@hlfv1 --networkAdmin admin --networkAdminEnrollSecret adminpw --archiveFile land-registry@0.0.1.bna --file networkadmin.card

And that's all you need, assuming you've done all the steps in the previous article before. If you only do those three commands without setting a proper environment, it will obviously not work.

The code

Note: I will link to the Github repository at the end of this article.

The application is rather simple. There is an index.js file.

const shell = require('shelljs')
const args = process.argv.slice(2)
const getREregistry = require('./getREregistry')
const getPIregistry = require('./getPIregistry')
const getPI = require('./getPI')
const contractLoan = require('./contractLoan')
const getLoans = require('./getLoans')
const getBanks = require('./getBanks')
const createPI = require('./createPI')
const createRE = require('./createRE')
const contractInsurance = require('./contractInsurance')
const getInsurances = require('./getInsurances')
const buyRealEstate = require('./buyRealEstate')
const getREAgents = require('./getREAgents')
const getNotaries = require('./getNotaries')

// get first argument
let arg = args.shift()
let realEstateId, duration, bankId, privateId, address, insuranceId

switch( arg ){
  case 'getAllRE':
    shell.exec('node getREregistry.js')
    process.exit()
    break
  case 'getAllPI': 
    shell.exec('node getPIregistry.js')
    process.exit()
    break
  case 'getREAgents':
    shell.exec('node getREAgents.js')
    process.exit()
    break
  case 'getInsurances':
    shell.exec('node getInsurances.js')
    process.exit()
    break
  case 'getNotaries': 
    shell.exec('node getNotaries.js')
    process.exit()
    break
  case 'getPI':
    const id = args.shift()
    shell.exec(`node getPI.js ${id}`)
    process.exit()
    break
  case 'getLoans':
    shell.exec('node getLoans.js')
    process.exit()
    break
  case 'getBanks':
    shell.exec('node getBanks.js')
    process.exit()
    break
  case 'createPI':
    privateId = args.shift()
    let name = args.shift()
    address = args.shift()
    let balance = args.shift()
    shell.exec(`node createPI.js ${privateId} ${name} ${address} ${balance}`)
    process.exit()
    break
  case 'createRE':
    let reId = args.shift()
    address = args.shift()
    let reSquareMeters = args.shift()
    let price = args.shift()
    let ownerId = args.shift()
    shell.exec(`node createRE.js ${reId} ${reAddress} ${reSquareMeters} ${price} ${ownerId}`)
    process.exit()
    break
  case 'contractLoan':
    let debtorId = args.shift()
    let bankId = args.shift()
    realEstateId = args.shift()
    let insterestRate = args.shift()
    duration = args.shift()
    shell.exec(`node contractLoan.js ${debtorId} ${bankId} ${realEstateId} ${insterestRate} ${duration}`)
    process.exit()
    break
  case 'contractInsurance':
    let insuredId = args.shift()
    insuranceId = args.shift()
    realEstateId = args.shift()
    cost = args.shift()
    duration = args.shift()
    shell.exec(`node contractInsurance.js ${insuredId} ${insuranceId} ${realEstateId} ${cost} ${duration}`)
    process.exit()
    break
  case 'buyRealEstate':
    let buyer = args.shift()
    let seller = args.shift()
    realEstateId = args.shift()
    let loan = args.shift()
    let realEstateAgent = args.shift()
    let notary = args.shift()
    insuranceId = args.shift()
    shell.exec(`node buyRealEstate.js ${buyer} ${seller} ${realEstateId} ${loan} ${realEstateAgent} ${notary} ${insuranceId}`)
    process.exit()
    break
  default:
    console.log('Wrong argument')
    process.exit()
    break
}

shell.exec('node index.js')

A GET route

We use shelljs to interact with the terminal. Depending on the argument you provide, we will execute a certain action. Some actions, when creating an asset or a participant, require additional arguments. Let's look at the getAllPI argument. PI stands for Private Individual, a participant in our network. When we provide this argument, we are going to retrieve every single Private Individual participant in the network. The action is described in the getPIRegistry.js file:

const BusinessNetworkConnection = require('composer-client').BusinessNetworkConnection
const Table = require('cli-table2')

const getPIregistry = (async function(){
  
  try {
    this.bizNetworkConnection = new BusinessNetworkConnection()
    let connection = await this.bizNetworkConnection.connect('admin@land-registry')
    let registry = await this.bizNetworkConnection.getParticipantRegistry('org.acme.landregistry.PrivateIndividual')
    let resources = await registry.getAll()
    let table = new Table({
      head: ['ID', 'Name', 'Address', 'Balance']
    })
    let arrayLength = resources.length
    for(let i = 0; i < arrayLength; i++) {
      let tableLine = []
      tableLine.push(resources[i].id)
      tableLine.push(resources[i].name)
      tableLine.push(resources[i].address)
      tableLine.push(resources[i].balance)
      table.push(tableLine)
    }
    console.log(table.toString())
    process.exit()
      
  } catch(error) {
    console.log(error)
    process.exit()
  }
}())

module.exports = getPIregistry

In order to interact with the Javascript API, we need only one package: composer-client. The structure is the same in every file. We connect to the private blockchain using the admin@land-registry admin card. I've put everything inside a IIFE ( Immediately Invoked Function Expression ) and I used the async/await keywords to make it clearer. The Javascript API uses promises, so you can chain the .then methods if you wish.

In our getPIRegistry file, we get the participant registry and call the getAll method on it. This will retrieve all the Private Individual participants. We then used the cli-table2 package to display the data in a nice table in our terminal.

A POST route

Create a Real Estate asset

To create a real estate asset, we use a command like this:

node index.js createRE id address squareMeters price ownerId

We need 5 parameters to create such an asset. The code is in the createRE.js file:

const BusinessNetworkConnection = require('composer-client').BusinessNetworkConnection

const createRE = (async function(){
  try {
    this.bizNetworkConnection = new BusinessNetworkConnection()
    let connection = await this.bizNetworkConnection.connect('admin@land-registry')
    const args = process.argv.slice(2)
    const reId = args.shift()
    const address = args.shift()
    const squareMeters = args.shift()
    const price = args.shift()
    const ownerId = args.shift()
    let factory = connection.getFactory()
    let re = factory.newResource('org.acme.landregistry', 'RealEstate', reId)
    re.address = address
    re.squareMeters = parseFloat(squareMeters)
    re.price = parseFloat(price)

    this.reRegistry = await this.bizNetworkConnection.getAssetRegistry('org.acme.landregistry.RealEstate')

    let ownerRelationship = factory.newRelationship('org.acme.landregistry', 'PrivateIndividual', ownerId)
    re.owner = ownerRelationship

    await this.reRegistry.add(re)
    console.log('Real Estate asset created!')
    process.exit()

  }catch( err ){
    console.log(err)
    process.exit()
  }
})()

module.exports = createRE

After the initial connection to the blockchain network, we retrieve the arguments we need. Then, we create a factory to create a new resource, in this case a RealEstate asset. We specify the relationship between the PrivateIndividual participant and this new RealEstate asset. Finally, after retrieving the RealEstate registry, we call the add method.

Note: You can add several assets or participants at once with the addAll method. This methods takes an array of the resources you want to add to the blockchain.

Submit a transaction

Last but not least, I will show you how to submit a transaction. The transaction will be triggered by this command:

node index.js buyRealEstate buyerId sellerId realEstateId loanId realEstateAgentId notaryId insuranceId

We need a few more arguments to complete this transaction, because there are quite a few relationships. You can go back to the previous article if you want to take a look at the business model we are using.

buyRealEstate.js

const BusinessNetworkConnection = require('composer-client').BusinessNetworkConnection

const contractInsurance = (async function(){
  try{
    this.bizNetworkConnection = new BusinessNetworkConnection()
    let connection = await this.bizNetworkConnection.connect('admin@land-registry')
    const args = process.argv.slice(2)
    const pIdBuyer = args.shift()
    const pIdSeller = args.shift()
    const realEstateId = args.shift()
    const loanId = args.shift()
    const realEstateAgentId = args.shift()
    const notaryId = args.shift()
    const insuranceId = args.shift()
    let transaction = {
      "$class": "org.acme.landregistry.BuyingRealEstate"
    }
    transaction.buyer = pIdBuyer
    transaction.seller = pIdSeller
    transaction.realEstate = realEstateId
    transaction.loan = loanId
    transaction.realEstateAgent = realEstateAgentId
    transaction.notary = notaryId
    transaction.insurance = insuranceId
    transaction.isNewOwnerMainResidence = false

    let serializer = connection.getSerializer()
    let resource = serializer.fromJSON(transaction)
    await this.bizNetworkConnection.submitTransaction(resource)
    console.log('Transaction Completed!')
    process.exit()
  }catch( err ){
    console.log(err)
    process.exit()
  }
})()

module.exports = contractInsurance

We start off the same, connecting to the blockchain and retrieving arguments. We then create a transaction object. Notice the $class key in the object. We get the serializer to transform our JSON into a resource Composer can understand. Finally we call the submitTransaction method.

Of course, before doing this transaction, you would need to contract a loan and an insurance. Both transactions can be created via the command line and you will find the code in the Github repository. To keep things short, I only show a few actions here.

Note: You could ( should ) add some validations in some actions ( make sure a Participant exists for example before specifying it in a transaction... ). I'll let you do that πŸ˜‰

Repository

The code can be found here. Feedbacks welcome πŸ˜ƒ

Have fun!

Discover and read more posts from Damien Cosset
get started