Hamsa Harcourt I'm Hamsa, a software engineer with a strong passion for building human-centric products. I love teaching concepts about JavaScript and the web at large.

How to write DApps on Corda

8 min read 2412

Corda Blockchain

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!

Introduction to Corda

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:

  • Assured identity: Parties will have confidence in the network’s participants’ identities. Identification in Corda is represented by a certificate issued by a relevant authority
  • Privacy: The only people that have access to transaction information are those who are involved in the transaction and those who need to verify transaction source
  • Interoperability: Corda is intended to allow numerous applications to coexist and interoperate on the same network; a defined set of contract interfaces is supplied to optimize interoperability from a wide range of providers

Key components of CorDapps

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:

  • States: States keep track of data between transactions and are immutable, which means they can’t be changed after they’ve been formed. Any modifications must instead result in the creation of a new successor state
  • Contracts: Contracts define the validation criteria that will be applied to transaction inputs and outputs. Contracts guarantee that transaction states are valid and that invalid transactions are avoided. One or more contracts may exist in a CorDapp, each of which provides rules for one or more states
  • Flows: Flows are the activities your CorDapp may take on a network, and they constitute your CorDapp’s business logic. Flows allow parties to coordinate their operations without the use of a central controller
  • Transactions: A transaction is a request to update the ledger. It consumes current input states and outputs new ones to update the ledger. Before a transaction is accepted by the nodes, a transaction must be unique, valid, and signed by the appropriate parties
  • Notary: A notary is a Corda network service that helps prevent the duplicate expenditure of network assets. It does so by guaranteeing that each transaction includes unique input states that have not been utilized by a previous transaction. A transaction is regarded as finished after it is signed by the notary
  • Consensus: In Corda, you may establish consensus by proving a transaction that is both valid and unique. Consensus is a method that allows nodes to agree on the network’s current state. Before a proposed transaction can be entered into the ledger, both parties must agree that it is legal

Getting started with Corda

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:

  • Java 8 JDK
  • IntelliJ IDEA
  • Git
  • Gradle, any version between 5.1 and 5.6.4

Setting up

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.

Create the State

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.

Create the contract

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.


More great articles from LogRocket:


With the code above, our contract performs the following checks:

  • Transactions must be signed by the issuer
  • Amount must be greater than zero
  • Only the issue command can be used
  • Zero initial input state since it is an issuance

Finally, 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.

Writing the initiating flow

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.

Writing the responder flow

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.

Running our CorDapp

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.

Launch the sample CorDapp

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.

Interacting with our CorDapp

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.

Conclusion

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.

Join organizations like Bitso and Coinsquare who use LogRocket to proactively monitor their Web3 apps

Client-side issues that impact users’ ability to activate and transact in your apps can drastically affect your bottom line. If you’re interested in monitoring UX issues, automatically surfacing JavaScript errors, and tracking slow network requests and component load time, try LogRocket.https://logrocket.com/signup/

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 — .

Hamsa Harcourt I'm Hamsa, a software engineer with a strong passion for building human-centric products. I love teaching concepts about JavaScript and the web at large.

Leave a Reply