Bitcoin’s launch on 3 January 2009 set trends like digital assets and digital currencies into motion. Since then, more blockchains like Ethereum and Solana have been created. Despite having different features and use cases, these blockchains have one thing in common: they were designed to operate democratically without regulators. Therefore, this model is not suitable for regulated industries, in which data must be kept confidential and shared between trusted parties. For this reason, private blockchains exist.
A private blockchain is a permissioned blockchain that contains entities called network operators. These control the network and are able to configure permissions and access the controls of the other nodes. To maintain privacy and trust, only the entities participating in a transaction will have knowledge of it.
A few examples of digital platforms that utilize private blockchains include Hyperledger Fabric, Ripple, and R3’s Corda. In this article, we’ll explore Corda, learning how to create CorDapps. Let’s get started!
Corda is a permissioned peer-to-peer (P2P) distributed ledger technology (DLT) that facilitates the development of applications in regulated markets. With Corda, parties may freely discover and transact with each other in a single, open network while having confidence in the identification of network participants.
In the long run, Corda aspires to be a shared worldwide distributed ledger. To do so, the many solutions that use Corda software must adhere to a set of common standards and criteria. Some of these criteria, often known as end-state principles, are as follows:
CorDapp is a shorthand for Corda distributed application. CorDapps are distributed applications that run on the Corda node. The following are the key components of a CorDapp:
We’ll create a CorDapp to model the issuance of tokens on the Corda blockchain. Our CorDapp will keep track of the issuer of the token, the holder, and the amount being issued.
There are four pieces of required software for CorDapp development:
To set up our CorDapp, first, clone the Corda Java template from their GitHub repo. Open cordapp-template-java
in any IDE of your choice. I’ll use IntelliJ IDE.
To reiterate, states are immutable and keep track of data between transactions. Our CorDapp is going to have a set of attributes that we‘ll store on our state, like issuer
, holder
, and amount
.
Navigate to /contracts/src/main/java/com/template/states
and create a new Java class called TokenState
:
package com.template.states import com.template.contracts.TokenContract; import net.corda.core.identity.AbstractParty; import net.corda.core.contracts.BelongsToContract; import net.corda.core.contracts.ContractState; import net.corda.core.identity.Party; import org.jetbrains.annotations.NotNull; import java.util.Arrays; import java.util.List; @BelongsToContract(TokenContract.class) public class TokenState implements ContractState { private Party issuer; private Party holder; private int amount; public TokenState(Party issuer, Party holder, int amount){ this.issuer = issuer; this.holder = holder; this.amount = amount; } public Party getHolder() { return holder; } public Party getIssuer() { return issuer; } public int getAmount() { return amount; } @NotNull @Override public List<AbstractParty> getParticipants() { return Arrays.asList(issuer, holder); } }
In the code above, we create a class called TokenState
that inherits from the ContractState
class. The ContractState
class tells Corda that we are implementing a state.
Next, we add the @BelongsToContract
annotation, which establishes the relationship between a state and a contract. Without this, your state doesn’t know which contract is used to verify it. Adding this annotation triggers an error in IntelliJ because we are yet to create our TokenContract
.
Then, we create three attributes, issuer
, holder
, and amount
. The issuer
and holder
attributes are given a type of Party
because they both represent entities on the node.
Next, we create three getter methods for each of the attributes. Finally, we create a getParticipants
method that defines what parties should be aware of the transaction. In our case, we only want the issuer
and the holder
to be aware of the transaction.
To reiterate, contracts define the rules of how states can evolve. They make certain validations before a transaction goes through successfully. Therefore, each state is linked to a contract.
Navigate to /contracts/src/main/java/com/template/contracts
and create a new Java class called TokenContract
:
package com.template.contracts import net.corda.core.contracts.CommandData; import net.corda.core.contracts.Contract; import net.corda.core.transactions.LedgerTransaction; import org.jetbrains.annotations.NotNull; import com.template.states.TokenState; public class TokenContract implements Contract { public static final String ID = "contracts.TokenContract"; @Override public void verify(@NotNull LedgerTransaction tx) { if(tx.getCommands().size() != 1) { throw new IllegalArgumentException("expects only one command: ISSUE"); } if(tx.getCommand(0).getValue() instanceof Commands.Issue){ throw new IllegalArgumentException("issue command expected"); } TokenState state = (TokenState) tx.getOutput(0); int amountIssued = state.getAmount(); if (amountIssued <= 0){ throw new IllegalArgumentException("amount must be greater than zero"); } if(! (tx.getCommand(0).getSigners().contains(state.getIssuer().getOwningKey()))){ throw new IllegalArgumentException("transaction must be signed by issuer"); } } // Used to indicate the transaction's intent. public interface Commands extends CommandData { class Issue implements Commands {} } }
In the code above, we create a class called TokenContract
that inherits from the Contract
class. The Contract
class tells Corda that we are implementing a contract.
First, we create an attribute called ID
that will identify our contract when building our transaction in testing environments. The ID
attribute is purely optional.
One of the methods given to us by the Contract
class we inherited is the verify
method, which we must override. The verify
method takes transactions as input and evaluates them against defined rules. A transaction is valid if the verify
method does not throw an exception.
With the code above, our contract performs the following checks:
issue
command can be usedFinally, since we intend to issue the token to another party, we create a class called Issue
that inherits from the Command
class. The Command
class is used to indicate the type of action being performed.
To reiterate, flows contain the business logic of our CorDapp. The initiator flow is run by the node initiating the transaction, which would be the issuer
in our case.
Navigate to workflows/src/main/java/com/template/flows
and create a new Java class called FlowInitiator
:
package com.template.flows; import co.paralleluniverse.fibers.Suspendable; import com.bootcamp.contracts.TokenContract; import com.bootcamp.states.TokenState; import net.corda.core.flows.*; import net.corda.core.identity.CordaX500Name; import net.corda.core.identity.Party; import net.corda.core.transactions.SignedTransaction; import net.corda.core.transactions.TransactionBuilder; import net.corda.core.utilities.ProgressTracker; import net.corda.core.contracts.CommandData; import static java.util.Collections.singletonList; @InitiatingFlow @StartableByRPC public static class TokenFlowInitiator extends FlowLogic<SignedTransaction> { private final Party owner; private final int amount; public TokenFlowInitiator(Party owner, int amount) { this.owner = owner; this.amount = amount; } private final ProgressTracker progressTracker = new ProgressTracker(); @Override public ProgressTracker getProgressTracker() { return progressTracker; } @Suspendable @Override public SignedTransaction call() throws FlowException { /** Explicit selection of notary by CordaX500Name - argument can by coded in flows or parsed from config (Preferred)*/ final Party notary = getServiceHub().getNetworkMapCache().getNotary(CordaX500Name.parse("O=Notary,L=London,C=GB")); // We get a reference to our own identity. Party issuer = getOurIdentity(); /* ============================================================================ * TODO 1 - Create our TokenState to represent on-ledger tokens! * ===========================================================================*/ // We create our new TokenState. TokenState tokenState = new TokenState(issuer, owner, amount); /* ============================================================================ * TODO 3 - Build our token issuance transaction to update the ledger! * ===========================================================================*/ // We build our transaction. TransactionBuilder transactionBuilder = new TransactionBuilder.setNotary(notary).addOutputState(tokenState).addCommand(new TokenContract.Commands.Issue(), Arrays.asList(issuer.getOwningKey(), owner.getOwningKey())); /* ============================================================================ * TODO 2 - Write our TokenContract to control token issuance! * ===========================================================================*/ // We check our transaction is valid based on its contracts. transactionBuilder.verify(getServiceHub()); FlowSession session = initiateFlow(owner); // We sign the transaction with our private key, making it immutable. SignedTransaction signedTransaction = getServiceHub().signInitialTransaction(transactionBuilder); // The counterparty signs the transaction SignedTransaction fullySignedTransaction = subFlow(new CollectSignaturesFlow(signedTransaction, singletonList(session))); // We get the transaction notarised and recorded automatically by the platform. return subFlow(new FinalityFlow(fullySignedTransaction, singletonList(session))); } }
In the code above, we create a class called TokenFlowInitiator
that inherits from the FlowLogic
class. The FlowLogic
class tells Corda that we are creating a Flow.
Then, we add two annotations, @InitiatingFlow
and @StartableByRPC
. The @InitiatingFlow
annotation indicates that this flow is the initiating flow. On the other hand, the @StartableByRPC
annotation allows RPC to start the flow.
Next, we create a variable called progressTracker
that checkpoints each stage of the flow and outputs the specified messages when each checkpoint is reached in the code.
Next, we create the call
method, which Corda calls when the flow is started. Inside the call
method, we first create a notary and store it in a variable called notary
. Since we are dealing with multiple parties, we need a notary service to reach consensus between the parties.
We get our identity and store it in a variable called issuer
. Next, we create our token by creating a new instance of TokenState
and pass the issuer
, owner
, and amount
as arguments.
Then, we build our transaction proposal by creating a new instance of TransactionBuilder
. We chain different methods to set our notary, add commands, and sign the transaction.
Finally, we start a FlowSession
with the counterparty using the InitiateFlow
method. This process enables us to send the state to the counterparty. We then call the CollectSignaturesFlow
subflow to collect signatures.
The responder flow is run by the counterparty. It receives and records the transaction, then responds to the issuer’s flow by sending back an acknowledgement if the transaction was successful.
Navigate to workflows/src/main/java/com/template/flows
and create a new Java class called TokenFlowResponder
:
package com.template.flows; import co.paralleluniverse.fibers.Suspendable; import net.corda.core.flows.*; import net.corda.core.identity.Party; import net.corda.core.transactions.SignedTransaction; @InitiatedBy(TokenFlowInitiator.class) public static class TokenFlowResponder extends FlowLogic<Void>{ //private variable private FlowSession counterpartySession; //Constructor public TokenFlowResponder(FlowSession counterpartySession) { this.counterpartySession = counterpartySession; } @Suspendable @Override public Void call() throws FlowException { SignedTransaction signedTransaction = subFlow(new SignTransactionFlow(counterpartySession) {}); //Stored the transaction into data base. subFlow(new ReceiveFinalityFlow(counterpartySession, signedTransaction.getId())); return null; } }
In the code above, we create a class called TokenFlowResponder
that inherits from the FlowLogic
class. We then add the @InitiatedBy
annotation and pass the TokenFlowInitiator
class as an argument, telling Corda who initiated the flow.
Then, we create the call
method with a subFlow
that will verify the transaction and the signatures it received from the flow initiator. Optionally, we can conventionally create a new method called checkTransaction
to perform a series of tests just to be safe.
To start our CorDapp, we have to first deploy it by navigating to the root of our project and running the following commands:
#Mac or Linux ./gradlew clean deployNodes #Windows gradlew.bat clean deployNodes
If our CorDapp builds successfully, it generates three nodes with the CorDapp installed on them. These can be found in the build/nodes
folder.
To start the nodes and our CorDapp, run the following command from our root directory:
#Mac or Linux ./build/nodes/runnodes #Windows .\build\nodes\runnodes.bat
The code above will start a new terminal window for each node. Give each terminal some time to start, and you’ll get a welcome message on the terminal once the node is ready.
To check if our CorDapp worked successfully, we can try to start a flow by running the following command:
flow start TokenFlowInitiator owner: PartyB, amount: 100
If it is successful, you’ll get a confirmation message.
The blockchain is a game-changing technology, and regulated businesses are not left out of this change thanks to systems that use private blockchains. Blockchain projects like Corda give businesses the flexibility they need while still keeping data private. In this article, we explored getting stared with Corda, learning how to make CorDapps.
I hope you enjoyed this tutorial, and feel free to leave a comment if you have any questions.
LogRocket is like a DVR for web and mobile apps, recording everything that happens in your web app or site. Instead of guessing why problems happen, you can aggregate and report on key frontend performance metrics, replay user sessions along with application state, log network requests, and automatically surface all errors.
Modernize how you debug web and mobile apps — Start monitoring for free.
Hey there, want to help make our blog better?
Join LogRocket’s Content Advisory Board. You’ll help inform the type of content we create and get access to exclusive meetups, social accreditation, and swag.
Sign up nowReact Native’s New Architecture offers significant performance advantages. In this article, you’ll explore synchronous and asynchronous rendering in React Native through practical use cases.
Build scalable admin dashboards with Filament and Laravel using Form Builder, Notifications, and Actions for clean, interactive panels.
Break down the parts of a URL and explore APIs for working with them in JavaScript, parsing them, building query strings, checking their validity, etc.
In this guide, explore lazy loading and error loading as two techniques for fetching data in React apps.