Building A Blockchain for Letter of Credit Using Hyperledger Fabric and Composer
Hyperledger is a set of open source tools and blockchain subprojects resulting from cross- industry collaboration. We will present two main components in this recipe – Hyperledger Fabric and Hyperledger Composer.
If you are not familiar with blockchain technology or Hyperledger family reading History and Evolution of Blockchain Technology from Bitcoin, Intro to Hyperledger Family and Hyperledger Blockchain Ecosystem, Hyperledger Design Philosophy and Framework Architecture, The Survey of Hyperledger Fabric Architecture and Components for Blockchain Developers and Overview of Building Blockchain Smart Contracts in Hyperledger articles are highly recommended.
If you like to explore blockchain development with an alternative platform like Ethereum (with Solidity programming), Blockchain Developer Guide- Introduction to Ethereum Blockchain Development with DApps and Ethereum VM, Building Ethereum Financial Applications with Java and Web3j API through Blockchain Oracles, Harness the Power of Distributed Storage IPFS and Swarm in Ethereum Blockchain Applications, and Building Enterprise Blockchain-as-a-Service Applications Using Ethereum and Quorum are highly recommended.
Hyperledger Fabric Overview
Hyperledger Fabric is the cornerstone of the Hyperledger projects hosted by the Linux Foundation. It is a permission-based blockchain, or more accurately a distributed ledger technology (DLT), which was originally created by IBM and Digital Asset. It is designed as a modular framework with different components, such as the orderer and Membership Services Provider (MSP). It is also a flexible solution, offering a pluggable consensus model, although it is currently only providing permissioned, voting-based consensus, with the assumption being that any current Hyperledger networks will be operating in a partially trustworthy environment.
Hyperledger Composer Overview
Alongside blockchain frameworks such as Fabric or Iroha, the Hyperledger project provides us with tools such as Composer, Hyperledger Explorer, and Cello. Composer provides a tool set to help build blockchain applications more easily. It consists of CTO (a modelling language), Playground (a browser-based development tool for rapid testing and deployment), and a command-line interface (CLI) tool. Composer supports the Hyperledger Fabric runtime and infrastructure, and internally the composer's APIs utilize the underlying Fabric API. Composer runs on Fabric, meaning the business networks generated by Composer can be deployed to Hyperledger Fabric for execution.
Important: following and doing Ultimate Guide for Building A Blockchain Supply Chain Using Hyperledger Fabric and Composer tutorial is required prior to proceeding with this recipe.
After finishing this recipe, you can move on to Build Blockchain Applications with Hyperledger Fabric and Composer on IBM Cloud tutorial that cover more advance topics on Hyperledger Fabric.
In this recipe, we will continue learning and exploring the essential components of the Hyperledger project by exploring an important financial concept—a letter of credit (LC). You will almost certainly come across an LC example while learning about blockchain's application to finance, as it provides a perfect example of how certain thorny issues can be resolved.
Throughout this recipe, we will deepen our knowledge of Hyperledger Fabric and introduce several advanced tools, while also learning how to develop and manage scalable, highly interoperable business solutions based on Hyperledger.
In this recipe, we will take a look at the following main topics:
- Hyperledger Composer
- Enrollment and identity management in a Hyperledger network
- Writing Chaincode for an LC
An LC is far too complex a process to be fully implemented and covered in a single recipe, hence, we will restrict our attention to how to implement the main concepts.
LC concepts and design
An LC, also known as a draft, a documentary credit, or bankers' commercial credit, is a letter from a bank guaranteeing that a buyer's payment to a seller will be received with a specified sum in a specified currency, provided the seller meets precisely defined terms within a fixed time frame. In the event that the buyer is unable to make the payment, the bank will cover the full or remaining amount of the purchase.
LCs are one of the most common payment methods available in international trade, as in such contexts, banking channels are used for payment between parties involved who may not trust each other. An LC issued by an issuing bank is often sent to a confirming bank that will undertake to pay the exporter according to the terms of the LC. The following diagram shows a simplified example of an LC process, although a real transaction could be much more complex:
The LC process is inefficient and typically time-consuming, as it depends on complex financial and administrative operations involving multiple actors. Blockchain technology can help to simplify the process by sharing financial data and documentation through a secure network that requires no third-party verification. It is reported that the global finance company BBVA was able to reduce the time required to send, verify, and authorize an international trade transaction, which normally takes from around 7 to 10 days, to just 2.5 hours by using the blockchain.
We will walk through an example of an LC by using Hyperledger Fabric technologies, and demonstrate how these technologies can improve the efficiency of the overall LC process.
Development environment
If you found Golang a difficult option to deal with when creating your Chaincode, then here is your relief. Instead of using Golang, we will discover how Hyperledger Composer can abstract Chaincode development using JavaScript and other easy-to-learn modeling languages.
Before getting started, make sure you have installed all the necessary prerequisites (except Golang) by following the instructions in our previous recipe.
Setting up the IDE
In the LC example, we will use Hyperledger Composer to work on our business network code and smart contract logic. For this, you can choose your preferred IDE, but for our example we will use Visual Studio Code. From https://code.visualstudio.com, download and install Visual Studio Code.
Open Visual Studio Code and navigate to View | Extensions, and search for and install the Hyperledger Composer extension. It will help you to write a composer's model files and report errors for you easily, as demonstrated in the following screenshot:
If you don't want to use an IDE, you can continue using your favorite text editor.
Getting Hyperledger Fabric running
In the previous recipe, we introduced how to build a Fabric network, and how to install and use the composer and playground utilities. In this recipe, we will discover a new way to start a Hyperledger Fabric V1.2 (or earlier) network for development purposes using a set of helper scripts (https://github.com/hyperledger/composer-tools/tree/master/ packages/fabric-dev-servers).
Let's start by creating a directory called fabric-dev-servers/ under the home (~) location:
mkdir ~/fabric-dev-servers && cd ~/fabric-dev-servers
Next, we install Yeoman, which is a tool for generating skeleton web applications. We install it so that we may use the generator generator-hyperledger-composer later:
npm install –g yo
Next, we install generator-hyperledger-composer mentioned in the previous step, which is a Yeoman module serving to create project templates for use with Hyperledger Composer:
npm install –g generator-hyperledger-composer
Afterward, install Hyperledger Fabric in the new directory using the following command. Make sure that both Bash and Docker are installed on the target system before running the script:
curl -O
https://raw.githubusercontent.com/hyperledger/composer-tools/master/package s/fabric-dev-servers/fabric-dev-servers.tar.gz
tar -xvf fabric-dev-servers.tar.gz
You'll get a dev server package containing scripts to set up three different levels of Fabric—versions 1.0, 1.1, and 1.2. You can select a version by setting the environment variable HL_FABRIC_VERSION to hlfv1* (for example, hlfv11 for version 1.1).
Run ./teardownAllDocker.sh and ./teardownFabric to make sure any previous Docker or Fabric runtimes are cleaned up:
sudo ./teardownAllDocker.sh ./teardownFabric.sh
You'll be asked in the prompt to choose between three options. You can choose the option kill and remove to clean all of the containers.
Afterwards, run ./downloadFabric.sh to download a local Hyperledger Fabric runtime:
sudo ./downloadFabric.sh
This script will download a set of runtime Docker images for the Hyperledger project representing the core Fabric components, which will comprise our Hyperledger Fabric network—Fabric CA, Fabric Peer, Fabric Chaincode environment, Couchdb, and Fabric Orderer.
All set? Now, let's start building our composer LC application.
Creating a composer Fabric application
As we introduced briefly in the previous recipe, Hyperledger Composer is built on top of the Hyperledger Fabric blockchain. It provides a set of tools to help developers rapidly develop use cases and deploy a blockchain solution quickly. Hyperledger Composer is built with JavaScript, leveraging modern tools including Node.js and NPM. It involves a business-centric, driven development process and helps developers to digitize and model business networks.
Hyperledger Composer is a toolbox of helpful tools that includes the following:
- modeling language called CTO
- Composer Playground, which is a web interface for the rapid building and testing of a business network
- Command-line interface (CLI) tools for integrating modeled business networks (created using Hyperledger Composer) in a running instance of the Hyperledger Fabric network
We introduced the Composer Playground in the previous recipe. In this recipe, we will discover the CTO and CLI tools.
Creating our first business network using Hyperledger Composer
Hyperledger Composer helps you to model your current business network quickly, which involves identifying the parties doing business, and their roles. Multiple participants will access the business network, and each maintainer of the network will host several peer nodes, which will replicate the ledger data between them.
If you have run through the example in the previous recipe, navigate to the blockchain- by-example/ folder, and create a folder called letterofcredit/. If you don’t have a blockchain-by-example/ folder, just create the letterofcredit/ folder at a location you prefer.
Use yo hyperledger-composer:businessnetwork to generate a business network project template with Yeoman's help. You'll need to answer a few questions about your new application:
If you choose to generate an empty template, you'll get a set of files, as shown in the following screenshot:
Let's look at each part of the Composer network definition.
Models definition
Composer helps you to model business networks representing your assets and the transactions related to them using model files. Such files have a .cto file suffix and they are written using a special object-oriented modeling language (called CTO). Generally, a modeling file is composed of the following elements:
- A single namespace
- A set of resource definitions, including assets, participants, transactions, and events
- Optional imported resources and declarations from other namespaces
To give you an idea of how modeling works, let's start with a simple example, introducing the basic concepts before building the LC business network.
Open the org.example.lc.cto file generated by Yoeman and paste in the following code snippets:
namespace org.example.lc
asset SampleAsset identified by assetId { o String assetId
o String value
--> SampleParticipant owner
}
transaction TransactionExample {
--> SampleAsset asset o String newValue
}
event AssetEvent{
-->SampleAsset asset
}
participant SampleParticipant identified by participantId { o String participantId
}
In this simple model, we define four different types of objects, representing an asset, transaction, participant, and an event.
An asset, for example SampleAsset, is defined using the asset keyword and identified by syntax. An asset contains fields, where each field, as for other objects, is expressed in the format o Fieldtype fieldname.
Here, we also model a transaction called TransactionExample that contains a relationship to an existing instance of SampleAsset (asset), which will be changed, and a string value (newValue) used to update the asset's property (value). A relationship is a typed pointer to an instance represented in the model by an arrow, -->, pointing to the object.
Using the same syntax as for asset and transaction, we define a participant type using the participant keyword.
JavaScript transaction logic
After we have modeled the example's participants, assets, and transactions, it's time to encode the business logic in the form of transaction processor functions. These functions are typically considered as a Chaincode or smart contract function.
For that purpose, create a new directory called lib/, under which we will define the JavaScript files written in ECMAScript ES5. This script (or scripts) will contain transaction processor functions that will be called when a transaction is submitted.
In your lib/ directory, create a logic.js file (optionally from Visual Studio Code), and then paste in the following transaction processor function:
/**
- Create the sample asset
- @param {org.example.lc.cto.TransactionExample} tx- the TransactionExample transaction
- @transaction
*/
async function TransactionExample (tx) {
// Get the factory.
var factory = getFactory();
// Create a new vehicle.
var asset= factory.newResource('org.example.lc', 'SampleAsset', 'ASSET_1');
// Create a new relationship to the owner. asset.owner=
factory.newRelationship(namespace,'SampleParticipant',tx.owner.getIdentifie r());
// Get the asset registry for the asset.
let assetRegistry = getAssetRegistry('org.example.lc.SampleAsset');
// Update the asset in the asset registry. await assetRegistry.add(tx.asset);
// Emit an event for the new created asset.
let event = getFactory().newEvent('org.example.lc', 'SampleEvent');
event.asset = tx.asset; emit(event);
}
In the preceding example, the first line comment contains a human-readable description of what TransactionExample does. The second line must include the @param annotation to indicate which resource name of the transaction defined in the model file will be triggered by this transaction processor function. After the resource name, we define the parameter name (tx in this case), which must be supplied to the JavaScript function as an argument. The last line must contain the @transaction annotation to indicate that this function is defined as a transaction processor function.
The transaction processor function defines the TransactionExample type as the associated transaction and passes the parameter, tx.
The getFactory and newResource functions are used to create a new instance of the asset SampleAsset. The properties of the newly created instance can be set as standard JavaScript object properties, for instance, asset.value=xyz.
We instantiate a new relationship using newRelationship with the given namespace, type, and identifier to point at an existing instance of the specified namespace and identifier.
Finally, once we have updated its attribute, we store the new instance in the appropriate asset registry using the Add function (from AssetRegistry API), and then an event is emitted.
Access control definition
It's possible, optionally, to define an access control list (ACL) to set the permissions of the business network. The rules in the ACL can determine which user (roles) are permitted to create, read, update, or delete the business network domain model elements. There are two types of ACL rules: simple and conditional.
Simple ACL rules are used to control the resources that participants can access, for example:
rule ExampleSimpleRule {
description: "Example Description of Simple Rule" participant: "org.example.SampleParticipant" operation: ALL
resource: "org.example.SampleAsset"
action: ALLOW
}
The preceding simple rule, shows that the participant SampleParticipant can perform all operations on the resources of the org.example.SampleAsset asset.
Conditional rules can specify the rules to apply depending on variable conditions. If the transaction is defined in ACL rules, when a participant submits a transaction, the ACL rule applies and only allows the participant to access the resources defined by the conditions:
rule ExampleConditionalRuleWithTransaction {
description: "Description of the Condition Rule With Transaction" participant(u): "org.example.SampleParticipant"
operation: READ, CREATE, UPDATE resource(m): "org.example.SampleAsset"
transaction(tx): "org.example.TransactionExample" condition: (m.owner.getIdentifier() == u.getIdentifier()) action: ALLOW
}
The preceding example shows that a participant user can perform all operations on the resource of the org.example.SampleAsset asset if the participant is the owner of the asset and he submitted a transaction called org.example.TransactionExample to perform the operation.
In our case, for the sake of simplicity, we opened the permissions for all participants, but, in a real-world project, you should define a detailed ACL rule to limit participant access to the resources and transactions:
/**
- Access control rules for lc-network
*/
rule Default {
description: "Allow all participants access to all resources" participant: "ANY"
operation: ALL
resource: "org.example.lc.*" action: ALLOW
}
rule SystemACL {
description: "System ACL to permit all access" participant: "ANY"
operation: ALL
resource: "org.hyperledger.composer.system.**" action: ALLOW
}
We just developed a minimalistic Hyperledger Composer business network model, Chaincode, and permissions ACL.
As you now have an idea about how we can model a business network, let's extend this model to implement our letter of credit use case. We first need to keep the same ACL file and you can also optionally clean up the other files (logic.js and model.cto)
LC business network
Until now, we have covered the basic, necessary Hyperledger Composer business network components, namely, the model, script, and permission ACL. Now, it's time to start developing our LC use case. We will be basing this on a sample generated previously by Yeoman and the modeling elements presented earlier.
Initial LC model
As presented earlier in the LC concepts and design section, we have four actors in our LC use case, namely, a buyer, a seller, an issuing bank, and a confirming bank. Let's define these participants in our CTO model as follows:
namespace org.example.lc enum ParticipantType {
- BUYER
- SELLER
- ISSUING_BANK
- CONFIRMING_BANK
}
// PARTICIPANTS
//BANK
participant Bank identified by bankID { o String bankID
- String name
- ParticipantType type
}
//USER
participant User identified by userId { o String userId
- String name
- String lastName optional
- String companyName
- ParticipantType type
--> Bank bank
}
We first define four types of participants—a buyer, seller, issuing bank, and confirming bank—in an enumeration, which is declared using the keyword enum ParticipantType. Then, we define a bank participant with the attribute ParticipantType to differentiate between the issuing and confirming banks.
A participant User, is defined with the property userId as the identity field, the associated company name, and the bank he will deal with. For example, the buyer will deal with the issuing bank and the seller will deal with the confirming bank.
Now, let’s define the letter of credit asset:
// ENUMS
enum LCStatus { o CONTRACT
- REQUEST_LC
- ISSUE_LC
- ADVICE_LC
- DELIVER_PRODUCT
- PRESENT_DOCUMENT
- DELIVERY_DOCUMENT
- BUYER_DEBIT_PAYMENT
- BANKS_PAYMENT_TRANSFER
- SELL_RECEIVED_PAYMENT
- CLOSED
}
// ASSETS
asset LetterOfCredit identified by letterId { o String letterId
--> User buyer
--> User seller
--> Bank issuingBank
--> Bank confirmingBank o Rule[] rules
- ProductDetails productDetails
- String [] evidence
- LCStatus status
- String closeReason optional
}
concept ProductDetails { o String productType
- Integer quantity
- Double pricePerUnit
}
concept Rule {
- String ruleId
- String ruleText
}
As we saw in the LC concepts and design section, the entire LC process has 10 steps. Therefore, we have defined the possible status in the LCStatus enumeration. We have also added a final CLOSED status to signal the end of the LC process.
We define the ProductDetails concept, which contains information on the kind of product and total price that the buyer is paying to the seller. In Composer's modeling language, concepts are abstract classes contained by an asset, participant, or transaction.
We model the LC as an asset with an ID, letterId, which can be used by all participants in the network to trace this LC. The LC is related (relationship) to the four participants, and defines certain rules (Rule[] rules) that only permitted, authorized parties can perform. The evidence array provides proof of certain steps needed to display the required documents. The variable LCStatus will keep track of the current blockchain LC status (as defined in the LCStatus enum).
We have defined the assets and participants, so now it is time to define the requisite transactions following the LC process steps.
Participant onboarding
Before the LC process starts, we need to onboard all participants in the network. For demonstration purposes, in this example, we will suppose we have a buyer with the name David Wilson, a seller, Jason Jones, the issuing bank (First Consumer Bank), and the confirming bank (Bank of Eastern Export).
We declare a transaction in the model file. We then define the corresponding transaction processor function in lib/logic.js as follows:
/**
- Create the participants needed for the demo
- @param {org.example.lc.CreateDemoParticipants} createDemoParticipants -
- the CreateDemoParticipants transaction
- @transaction
*/
async function createDemoParticipants() { // eslint-disable-line no- unused-vars
const factory = getFactory();
const namespace = 'org.example.lc';
// create the banks
const bankRegistry = await getParticipantRegistry(namespace + '.Bank');
const issuingbank = factory.newResource(namespace, 'Bank', 'BI'); issuingbank.name = 'First Consumer Bank';
issuingbank.type = 'ISSUING_BANK'; await bankRegistry.add(issuingbank);
const confirmingbank = factory.newResource(namespace, 'Bank', 'BE'); confirmingbank.name = 'Bank of Eastern Export';
confirmingbank.type = 'CONFIRMING_BANK'; await bankRegistry.add(confirmingbank);
// create users
const userRegistry = await getParticipantRegistry(namespace + '.User');
const buyer = factory.newResource(namespace, 'User', 'david'); buyer.name = 'David';
buyer.lastName= 'Wilson';
buyer.bank = factory.newRelationship(namespace, 'Bank', 'BI'); buyer.companyName = 'Toy Mart Inc';
buyer.type = 'BUYER';
await userRegistry.add(buyer);
const seller = factory.newResource(namespace, 'User', 'jason'); seller.name = 'Jason';
seller.lastName= 'Jones';
seller.bank = factory.newRelationship(namespace, 'Bank', 'EB'); seller.companyName = 'Valley Toys Manufacturing';
seller.type = 'SELLER';
await userRegistry.add(seller);
}
Here, we define an asynchronous function instantiating the objects (participants) representing the actors involved in the LC and add them to the participant registry.
As discussed earlier, there are 10 steps in the LC process flow. Therefore, we will add the related model objects and define the transaction logic for each step.
Initial agreement
As defined in our LC design, the first step in the agreement between buyer and seller is where the buyer agrees to purchase the goods from the seller. Therefore, we define an InitialApplication transaction in our model for all attending participants, including the buyer, seller, and banks. In this step, we define a transaction, which creates a letter of credit with an ID, letterId, and then define the related participants along an event firing a notification regarding the LC creation:
transaction InitialApplication { o String letterId
--> User buyer
--> User seller
--> Bank issuingBank
--> Bank confirmingBank o Rule[] rules
- ProductDetails productDetails
}
event InitialApplicationEvent {
--> LetterOfCredit lc
}
The following function represents the process behind the InitialApplication transaction as follows:
#related the transaction processor function
/**
- Create the LC asset
- @param {org.example.lc.InitialApplication} initalAppliation - the InitialApplication transaction
- @transaction
*/
async function initialApplication(application) { // eslint-disable-line no- unused-vars
const factory = getFactory();
const namespace = 'org.example.lc';
const letter = factory.newResource(namespace, 'LetterOfCredit', application.letterId);
letter.buyer = factory.newRelationship(namespace, 'User', application.buyer.getIdentifier());
letter.seller = factory.newRelationship(namespace,
'User',application.seller.getIdentifier()); letter.issuingBank =
factory.newRelationship(namespace,'Bank', application.buyer.bank.getIdentifier());
letter.confirmingBank = factory.newRelationship(namespace,'Bank',application.seller.bank.getIdentif ier());
letter.rules = application.rules; letter.productDetails = application.productDetails; letter.evidence = [];
letter.status = 'CONTRACT'; letter.step=0;
//save the application const assetRegistry = await
getAssetRegistry(letter.getFullyQualifiedType()); await assetRegistry.add(letter);
// emit event
const applicationEvent = factory.newEvent(namespace, 'InitialApplicationEvent');
applicationEvent.lc = letter; emit(applicationEvent);
}
When this function is called, it instantiates a new resource representing our new LC, and then defines the relationship between the LC and the participants using the newRelationship method. Afterward, it sets the letter status to CONTRACT, and LC step to 0 (letter.step=0).
We use the getAssetRegistry method to find the current LC (modeled as an asset in the CTO) in the blockchain using the getfullyQualifiedType method, which returns the fully qualified type name of the indicated instance. Then, we use the add function to add the LC asset to the blockchain.
At the end, we create an event object for the new asset using newEvent with the event namespace, type (InitialApplicationEvent), and then publish the event using the emit function.
LC request
After the initial agreement, the buyer requests an LC from the issuing bank by signing the bank’s LC form. We define the BuyerRequestLC transaction and the related event in the model CTO file as follows:
transaction BuyerRequestLC {
--> LetterOfCredit lc
--> User buyer
}
event BuyerRequestLCEvent {
--> LetterOfCredit lc
--> User buyer
}
In the transaction processor function, we check if the letter's status is not closed or under creation before updating its status to REQUEST_LC, and the step to 1, as follows:
/**
- Buyer submit LC requst to issuing bank
- @param {org.example.lc.BuyerRequestLC} buyerLCRequest - the Buyer request LC transaction
- @transaction
*/
async function buyerLCRequest(request) { // eslint-disable-line no-unused- vars
const factory = getFactory();
const namespace = 'org.example.lc'; let letter = request.lc;
if (letter.status === 'CLOSED') {
throw new Error ('This letter of credit has already been closed');
} else if (letter.step !== 0) {
throw new Error ('This letter of credit should be in step 0 - CONTRACT');
}
letter.status = 'REQUEST_LC'; letter.step = 1;
const assetRegistry = await getAssetRegistry(letter.getFullyQualifiedType());
await assetRegistry.update(letter);
// emit event
const buyerRequestLCEvent = factory.newEvent(namespace, 'BuyerRequestLCEvent');
buyerRequestLCEvent.lc = letter; emit(buyerRequestLCEvent);
}
As you will have noticed, Composer provides us with the ability to handle exceptions in transaction processor functions using throw new Error with a detailed message. Once thrown, the transaction will fail and roll back any changes already made.
The rest of the code is pretty similar to what we have done for the previous function, except here we use the update function to update the LC asset in the blockchain.
LC approval
After the buyer submits an LC request, the issuing bank approves the LC form, and sends it to the confirming bank. In our model file, we define the related IssuingBankApproveLC transaction and the related event as follows:
transaction IssuingBankApproveLC {
--> LetterOfCredit lc
}
event IssuingBankApproveLCEvent {
--> LetterOfCredit lc
}
The transaction logic here is similar to the previous step, where we use getAssetRegistry to find the existing LC asset and we update the LC status, before emitting the IssuingBankApproveLCEvent. The letter status is set to ISSUE_LC, and the LC step to 2:
/**
- issuing bank approval buyer LC
- @param {org.example.lc.IssuingBankApproveLC} issuingBankApproveLC -
- Issuing Bank approval LC transaction
- @transaction
*/
async function issuingBankApproveLC(request) { // eslint-disable-line no- unused-vars
const factory = getFactory();
const namespace = 'org.example.lc'; let letter = request.lc;
if (letter.status === 'CLOSED') {
throw new Error ('This letter of credit has already been closed');
} else if (letter.step!== 1) {
throw new Error ('This letter of credit should be in step 1 - REQUEST_LC');
}
letter.status = 'ISSUE_LC'; letter.step=2;
const assetRegistry = await
getAssetRegistry(request.lc.getFullyQualifiedType()); await assetRegistry.update(letter);
// emit event
const issuingBankApproveLCEvent = factory.newEvent(namespace, 'IssuingBankApproveLCEvent');
issuingBankApproveLCEvent.lc = letter; emit(issuingBankApproveLCEvent);
}
LC advising
Once the issuing bank approves the LC form, the confirming bank receives LC advice and sends it to the seller. We define the ConfirmingBankAdviceLC transaction and an event for the LC update as follows:
transaction ConfirmingBankAdviceLC {
--> LetterOfCredit lc
}
event ConfirmingBankAdviceLCEvent {
--> LetterOfCredit lc
}
The letter status is set to ADVISE_LC, and the step property is updated to 3. We define the related transaction, along with an event firing a notification about the LC advised by the confirming bank:
/**
- confirming bank approval LC
- @param {org.example.lc.ConfirmingBankAdviceLC} confirmingBankAdviceLC -
- confirming bank advice LC transaction
- @transaction
*/
async function confirmingBankAdviceLC(request) { // eslint-disable-line no- unused-vars
const factory = getFactory();
const namespace = 'org.example.lc'; let letter = request.lc;
if (letter.status === 'CLOSED') {
throw new Error ('This letter of credit has already been closed');
} else if (letter.step!== 2) {
throw new Error ('This letter of credit should be in step 2 - ISSUE_LC');
}
letter.status = 'ADVICE_LC'; letter.step=3;
const assetRegistry = await getAssetRegistry(request.lc.getFullyQualifiedType());
await assetRegistry.update(letter);
// emit event
const confirmingBankAdviceLCEvent =
factory.newEvent(namespace, 'ConfirmingBankAdviceLCEvent'); confirmingBankAdviceLCEvent.lc = letter; emit(confirmingBankAdviceLCEvent);
}
Goods shipping
After the seller receives LC advice from the confirming bank, the seller ships the goods. In this step, we define the SellerDeliverGoods transaction and related event for the LC update:
transaction SellerDeliverGoods {
--> LetterOfCredit lc o String evidence
}
event SellerDeliverGoodsEvent {
--> LetterOfCredit lc
}
The letter status is set to DELIVER_PRODUCT, and the step to 4:
/**
- seller deliver product
- @param {org.example.lc.SellerDeliverGoods} sellerDeliverGoods - seller deliver product
- @transaction
*/
async function sellerDeliverGoods(request) { // eslint-disable-line no- unused-vars
const factory = getFactory();
const namespace = 'org.example.lc'; let letter = request.lc;
if (letter.status === 'CLOSED') {
throw new Error ('This letter of credit has already been closed');
} else if (letter.step!== 3) {
throw new Error ('This letter of credit should be in step 3 - ADVICE_LC');
}
letter.status = 'DELIVER_PRODUCT'; letter.step=4; letter.evidence.push(request.evidence); const assetRegistry = await
getAssetRegistry(request.lc.getFullyQualifiedType()); await assetRegistry.update(letter);
// emit event
const sellerDeliverGoodsEvent = factory.newEvent(namespace, 'SellerDeliverGoodsEvent');
sellerDeliverGoodsEvent.lc = letter; emit(sellerDeliverGoodsEvent);
}
Present document
After the seller has shipped the goods, he presents a written document to the confirming bank. Therefore, we define the SellerPresentDocument transaction and related event for the LC update:
transaction SellerPresentDocument {
--> LetterOfCredit lc o String evidence
}
event SellerPresentDocumentEvent {
--> LetterOfCredit lc
}
The letter status is set to PRESENT DOCUMENT, and the step to 5:
/**
- seller Presentation the Document
- @param {org.example.lc.SellerPresentDocument} sellerPresentDocument - seller present document
- @transaction
*/
async function sellerPresentDocument(request) { // eslint-disable-line no- unused-vars
const factory = getFactory();
const namespace = 'org.example.lc'; let letter = request.lc;
if (letter.status === 'CLOSED') {
throw new Error ('This letter of credit has already been closed');
} else if (letter.step!== 4) {
throw new Error ('This letter of credit should be in step 4 - ADVICE_LC');
}
letter.status = 'PRESENT_DOCUMENT'; letter.step=5; letter.evidence.push(request.evidence); const assetRegistry = await
getAssetRegistry(request.lc.getFullyQualifiedType()); await assetRegistry.update(letter);
// emit event
const sellerPresentDocumentEvent = factory.newEvent(namespace, 'SellerPresentDocumentEvent');
sellerPresentDocumentEvent.lc = letter; emit(sellerPresentDocumentEvent);
}
The LC asset has an evidence array data field, which contains all the document proofs for the entire process. When a seller presents a written document to the confirming bank, the document will be added to the LC evidence using the JavaScript push function: letter.evidence.push(request.evidence). As you know, the push() method adds new items to the end of an array.
Document delivery
At this step, the confirming bank delivers the document to the issuing bank. We define the ConfirmingBankDeliverDocument transaction and related event and update the LC status with the document evidence:
transaction ConfirmingBankDeliverDocument {
--> LetterOfCredit lc o String evidence
}
event ConfirmingBankDeliverDocumentEvent {
--> LetterOfCredit lc
}
The letter status is set to DELIVERY_DOCUMENT, and the step is 6:
/**
- seller deliver product
- @param {org.example.lc.ConfirmingBankDeliverDocument} confirmingBankDeliverDocument -
(seller deliver product
- @transaction
*/
async function confirmingBankDeliverDocument(request) { // eslint-disable- line no-unused-vars
const factory = getFactory();
const namespace = 'org.example.lc'; let letter = request.lc;
if (letter.status === 'CLOSED') {
throw new Error ('This letter of credit has already been closed');
} else if (letter.step!== 5) {
throw new Error ('This letter of credit should be in step 5 - PRESENT_DOCUMENT');
}
letter.status = 'DELIVERY_DOCUMENT'; letter.step=6; letter.evidence.push(request.evidence); const assetRegistry = await
getAssetRegistry(request.lc.getFullyQualifiedType()); await assetRegistry.update(letter);
// emit event
const confirmingBankDeliverDocumentEvent = factory.newEvent(namespace,'ConfirmingBankDeliverDocumentEvent');
confirmingBankDeliverDocumentEvent.lc = letter; emit(confirmingBankDeliverDocumentEvent);
}
Debit payment
The buyer makes a payment for the goods. In this step, we define the
BuyerDepositPayment transaction and related event and update the LC status:
transaction BuyerDepositPayment {
--> LetterOfCredit lc
}
event BuyerDepositPaymentEvent {
--> LetterOfCredit lc
}
The letter status is set to BUYER_DEBIT_PAYMENT, and the step to 7:
/**
- buyer Deposit Payment
- @param {org.example.lc.BuyerDepositPayment} buyerDepositPayment - buyer Deposit Payment
- @transaction
*/
async function buyerDepositPayment(request) { // eslint-disable-line no- unused-vars
const factory = getFactory();
const namespace = 'org.example.lc'; let letter = request.lc;
if (letter.status === 'CLOSED') {
throw new Error ('This letter of credit has already been closed');
} else if (letter.step!== 6) {
throw new Error ('This letter of credit should be in step 6 - DELIVERY_DOCUMENT');
}
letter.status = 'BUYER_DEBIT_PAYMENT'; letter.step=7;
const assetRegistry = await getAssetRegistry(request.lc.getFullyQualifiedType());
await assetRegistry.update(letter);
// emit event
const buyerDepositPaymentEvent = factory.newEvent(namespace, 'BuyerDepositPaymentEvent');
buyerDepositPaymentEvent.lc = letter; emit(buyerDepositPaymentEvent);
}
Payment transfer
When the buyer receives the goods, the issuing bank transfers payment to the confirming bank. In this step, we define the BanksTransferPayment transaction and related event and update the LC status:
transaction BanksTransferPayment {
--> LetterOfCredit lc
}
event BanksTransferPaymentEvent {
--> LetterOfCredit lc
}
The letter status is set to BANKS_PAYMENT_TRANSFER, and the step to 8:
/**
- banks Transfer Payment
- @param {org.example.lc.BanksTransferPayment} banksTransferPayment - banks Transfer Payment
- @transaction
*/
async function banksTransferPayment(request) { // eslint-disable-line no- unused-vars
const factory = getFactory();
const namespace = 'org.example.lc'; let letter = request.lc;
if (letter.status === 'CLOSED') {
throw new Error ('This letter of credit has already been closed');
} else if (letter.step!== 7) {
throw new Error ('This letter of credit should be in step 6 - BUYER_DEBIT_PAYMENT');
}
letter.status = 'BANKS_PAYMENT_TRANSFER'; letter.step=8;
const assetRegistry = await getAssetRegistry(request.lc.getFullyQualifiedType());
await assetRegistry.update(letter);
// emit event
const banksTransferPaymentEvent = factory.newEvent(namespace, 'BanksTransferPaymentEvent');
banksTransferPaymentEvent.lc = letter; emit(banksTransferPaymentEvent);
}
Pay the seller
After receiving the payment from the issuing bank, the confirming bank makes the payment to the seller. In this step, we define the sellerReceivedPayment transaction and related event and update the LC status:
transaction SellerReceivedPayment {
--> LetterOfCredit lc
}
event SellerReceivedPaymentEvent {
--> LetterOfCredit lc
}
The letter status is set to SELL_RECEIVED_PAYMENT, and the step to 9:
/**
- seller Received Payment
- @param {org.example.lc.SellerReceivedPayment} sellerReceivedPayment - seller Received Payment
- @transaction
*/
async function sellerReceivedPayment(request) { // eslint-disable-line no- unused-vars
const factory = getFactory();
const namespace = 'org.example.lc'; let letter = request.lc;
if (letter.status === 'CLOSED') {
throw new Error ('This letter of credit has already been closed');
} else if (letter.step!== 8) {
throw new Error ('This letter of credit should be in step 6 - BANKS_PAYMENT_TRANSFER');
}
letter.status = 'SELL_RECEIVED_PAYMENT'; letter.step=9;
const assetRegistry = await getAssetRegistry(request.lc.getFullyQualifiedType());
await assetRegistry.update(letter);
// emit event
const sellerReceivedPaymentEvent = factory.newEvent(namespace,'SellerReceivedPaymentEvent');
sellerReceivedPaymentEvent.lc = letter; emit(sellerReceivedPaymentEvent);
}
LC closure
This will be the final step in the LC process, where the buyer receives the goods and the seller receives the payment. Therefore, we define a closing transaction referencing the targeted LC and the reason behind the closure:
transaction Close {
--> LetterOfCredit lc o String closeReason
}
event CloseEvent {
--> LetterOfCredit lc o String closeReason
}
The letter status is set to CLOSED, and the step to 10:
/**
- Close the LOC
- @param {org.example.lc.Close} close - the Close transaction
- @transaction
*/
async function close(closeRequest) { // eslint-disable-line no-unused-vars const factory = getFactory();
const namespace = 'org.example.lc'; let letter = closeRequest.lc;
if (letter.status === 'SELL_RECEIVED_PAYMENT') { letter.status = 'CLOSED';
letter.closeReason = closeRequest.closeReason;
// update the status of the lc const assetRegistry = await
getAssetRegistry(closeRequest.lc.getFullyQualifiedType()); await assetRegistry.update(letter);
// emit event
const closeEvent = factory.newEvent(namespace, 'CloseEvent'); closeEvent.lc = closeRequest.lc;
closeEvent.closeReason = closeRequest.closeReason;
emit(closeEvent);
} else if (letter.status === 'CLOSED') {
throw new Error('This letter of credit has already been closed');
} else {
throw new Error('Cannot close this letter of credit');
}
}
This was the final part of our Chaincode. Now, let's deploy it in the Fabric network and check whether it works as expected.
Deploying the LC
First off, we need to start Hyperledger Fabric. For that, change the directory to fabric- dev-servers/ and start the ./startFabric.sh script as follows:
cd ~/fabric-dev-servers sudo ./startFabric.sh
Next, create a peer admin card by executing the script ./createPeerAdminCard.sh. As a result, you'll get an output similar to the following:
If everything goes as expected, we can move on to building the business network archive.
Deploying business network
The first step is to create a business network archive from a directory on disk, by using the Composer command Line interface (Composer CLI) as follows:
composer archive create -t dir -n .
As a result, a business network archive file called lc-network@0.0.1.bna will be created in the lc-network/ folder. Next, we need to install the business network lc- network@0.0.1.bna archive file:
composer network install --card PeerAdmin@hlfv1 --archiveFile lc- network@0.0.1.bna
As you may have noticed, we need to specify a PeerAdmin business network card (using the --card option), which was created earlier when we ran ./createPeerAdminCard.sh. You can run composer card list to verify that you have the PeerAdmin card.
To start the business network, we run the following command:
composer network start --networkName lc-network --networkVersion 0.0.1 -- networkAdmin admin --networkAdminEnrollSecret adminpw --card PeerAdmin@hlfv1 --file networkadmin.card
As a result, a networkadmin.card file will be generated to authenticate the specified admin (--networkAdmin admin) later in the REST server.
Then, import the network administrator identity as a usable business card:
composer card import --file networkadmin.card
You can check that the business network has been deployed successfully using the following command:
composer network ping --card admin@lc-network
Generating a REST server
REST calls are a convenient way to communicate with the Fabric network. As we did in the previous recipe, we will generate a RESTful interface to access the business network by using the composer-rest-server tool. For that, navigate to the project folder and type the following command:
composer-rest-server
You'll be asked a few questions, for which you can provide the same answers as in the following screenshot:
As a next step, we will use the REST interface to test the LC business network we have developed. If you open a browser and navigate to http://youIpAddress:3000, you will see something similar to the following page:
You can expand each list of items (transactions, assets) on the page to invoke the associated transaction we defined in the network model. Let’s simulate an LC process and test each step using the REST interface.
Testing LC project
In this section, we will test the LC transactions in a sequential step-by-step manner. The steps are ordered following the order in which the transactions were implemented in the previous section. Therefore, we start with the CreateDemoParticipants transaction to onboard the parties involved.
Participant onboarding
In this test case, we onboard all participants in the network with the details we used previously in the demo: buyer (David Wilson), seller (Jason Jones), issuing bank (First Consumer Bank), and confirming bank (Bank of Eastern Export). For that, click on CreateDemoParticipants to submit a transaction, then expand the POST method, and then click the Try it out! button to invoke the transaction. If it succeeds, you'll see an HTTP 200 response code, as shown here:
If we get the/USER, we can see that both users are created in the blockchain.
If we GET the /BANK, we can see both banks are created in the blockchain.
Initial agreement
In this step, the buyer and seller agree that the buyer will purchase the goods, and afterward the InitialApplication transaction is submitted. The buyer and seller have uploaded the agreement rule associated with this newly created letter of credit.
Run the POST method for InitialApplication:
{
"$class": "org.example.lc.InitialApplication", "letterId": "LC-CA-501P10",
"buyer": "resource:org.example.lc.User#david", "seller": "resource:org.example.lc.User#jason", "issuingBank": "resource:org.example.lc.Bank#BI", "confirmingBank": "resource:org.example.lc.Bank#EB", "rules": [ {
"ruleId": "LC-CA-501P10-AGREEMENT-1",
"ruleText": "The Scooter will be received in working order"
},
{
"ruleId": "LC-CA-501P10-AGREEMENT-2",
"ruleText": "The Scooter will be received within 35 days"
}],
"productDetails": {
"$class": "org.example.lc.ProductDetails", "productType": "Scooter",
"quantity": 50000,
"pricePerUnit": 30
}
You can then verify the LC's content, and you should see a successful response with the status 200.
LC request
When the buyer requests an LC from the issuing bank by signing the bank’s LC form, the letter's status is set to REQUEST_LC.
To check that, click on the BuyerRequestLC POST method and provide the following data:
{
"$class": "org.example.lc.BuyerRequestLC",
"lc": "resource:org.example.lc.LetterOfCredit#LC-CA-501P10"
}
Here, we pass two JSON variables, $class and lc. The former is the transaction function's name we defined in the model file—BuyerRequestLC appended to the file namespace org.example.lc—whereas the latter specifies the targeted resource with the following structure:
"resource:{model name space}.{asset}#{assetId}".
As you may remember, in the previous step, we set the LC assetid as LC-CA-501P10. As a result, the Composer tool will find LC in the blockchain and update the LC status to
REQUEST_LC. If it succeeds, you'll see an HTTP 200 response code.
LC approval
At this level, the issuing bank approves the LC form, and sends it to the confirming bank. Therefore, the letter's status is set to ISSUE_LC, and provides the following data:
{
"$class": "org.example.lc.IssuingBankApproveLC",
"lc": "resource:org.example.lc.LetterOfCredit#LC-CA-501P10"
}
Click IssuingBankApproveLC—POST method. The passing value is similar to the previous step. We pass resource:org.example.lc.LetterOfCredit#LC-
CA-501P10 to specify the LC.
As a result, you should see that the letter's status is set to ISSUE_LC.
LC advising
Afterward, the confirming bank sends LC advice to the seller, and the letter status is set to
ADVISE_LC.
Click ConfirmingBankAdviceLC—POST method, with the following arguments:
{
"$class": "org.example.lc.ConfirmingBankAdviceLC",
"lc": "resource:org.example.lc.LetterOfCredit#LC-CA-501P10"
}
To verify the LC's content, you should see that the letter status is set to ADVISE_LC.
Goods shipping
At this stage, the seller ships the goods and the status is set to DELIVER_PRODUCT. Click
SellerDeliverGoods—POST method and provide the following data:
{
"$class": "org.example.lc.SellerDeliverGoods",
"lc": "resource:org.example.lc.LetterOfCredit#LC-CA-501P10", "evidence": "77603075985295a937b28cf0128f4e7f"
}
To verify the LC's content, you should see that the letter status is set to DELIVER_PRODUCT.
Present document
The seller presents a written document to the confirming bank and the status is set to
PRESENT_DOCUMENT.
Click SellerPresentDocument—POST method:
{
"$class": "org.example.lc.SellerPresentDocument",
"lc": "resource:org.example.lc.LetterOfCredit#LC-CA-501P10", "evidence": "acd2280df872c844ccfdf60ec7360819"
}
To verify the LC's content, you should see that the letter status is set to
PRESENT_DOCUMENT.
Deliver document
The confirming bank delivers the document to the issuing bank and the status is set to
DELIVERY_DOCUMENT.
Click ConfirmingBankDeliverDocument—POST method, and provide the following data:
{
"$class": "org.example.lc.ConfirmingBankDeliverDocument", "lc": "resource:org.example.lc.LetterOfCredit#LC-CA-501P10", "evidence": "a61524a8d2b5986c90dd3b84e8406290"
}
To verify the LC's content, you should see the letter status is set to DELIVERY_DOCUMENT.
Debit payment
The buyer makes the payment for the goods and the letter status is set to
BUYER_DEBIT_PAYMENT.
Click BuyerDepositPayment—POST method, and provide the following data:
{
"$class": "org.example.lc.BuyerDepositPayment",
"lc": "resource:org.example.lc.LetterOfCredit#LC-CA-501P10"
}
To verify the LC's content, you should see that the letter status is set to
BUYER_DEBIT_PAYMENT.
Payment transfer
The issuing bank transfers payment to the confirming bank and the letter status is set to
BANKS_PAYMENT_TRANSFER.
Click BanksTransferPayment—POST method, and provide the following data:
{
"$class": "org.example.lc.BanksTransferPayment",
"lc": "resource:org.example.lc.LetterOfCredit#LC-CA-501P10"
}
To verify the LC's content, you should see that the letter status is set to
BANKS_PAYMENT_TRANSFER.
Pay the seller
The confirming bank pays the payment to the seller and the letter status is set to
SELL_RECEIVED_PAYMENT.
Click SellerReceivedPayment—POST method, and provide the following data:
{
"$class": "org.example.lc.SellerReceivedPayment",
"lc": "resource:org.example.lc.LetterOfCredit#LC-CA-501P10"
}
To verify the LC's content, you should see that the letter status is set to
SELL_RECEIVED_PAYMENT.
LC closure
This is the final step in the LC process, where we send a closing transaction to update the letter's status to CLOSED.
Click Close—POST method, and provide the following data:
{
"$class": "org.example.lc.Close",
"lc": "resource:org.example.lc.LetterOfCredit#LC-CA-501P10", "closeReason": "LC completed"
}
If you were able to follow the instructions and successfully get to this step, congratulations! You are now able to build an application and generate a business domain REST API based on the deployed business network model!
Conclusion
In this recipe, we have learned about the Hyperledger Composer through the letter of credit example. We have defined and built a business network model and then packaged it as a .bna file and deployed it to a Fabric network. We also set up a Composer REST server to communicate with the business network over HTTP.
From now on, you should be able to build more complex blockchain projects using Hyperledger Fabric and Composer. If you have read the previous recipes, you'll now have an idea about the difference between business-driven blockhains, such as Hyperledger, and cryptocurrency-driven blockchains, such as Bitcoin or Ethereum.
For more hands-on tutorials and exercises on other projects of Hyperledger like Sawtooth or Iroha, visit Comprehensive Hyperledger Training Tutorials page to get the outline of our Hyperledger articles.
To conclude this recipe, we like to recommend Blockchain Hyperledger Development in 30 hours, Learn Hands-on Blockchain Ethereum Development & Get Certified in 30 Hrs, and Become Blockchain Certified Security Architect in 30 hours courses to those interested in pursuing a blockchain development career. This recipe is written by Brian Wu who is our senior Blockchain instructor & consultant in Washington DC. His books (Blockchain By Example and Hyperledger Cookbook) are highly recommended for learning more about blockchain development.
Brian Wu
July 2019