Private Blockchains: Hyperledger Composer Javascript API

Damien Cosset - Mar 26 '18 - - Dev Community

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 network install --card PeerAdmin@hlfv1 --archiveFile land-registry@0.0.1.bna

  • Finally, still from the land-registry folder, deploy the business network: composer network start --networkName land-registry --networkVersion 0.0.1 --networkAdmin admin --networkAdminEnrollSecret adminpw --card PeerAdmin@hlfv1 --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')
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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

Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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!

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .