Private Blockchains: Hyperledger Composer Javascript API
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!