Write states
This tutorial guides you through writing the two states you need in your CorDapp: AppleStamp
and BasketofApples
. You will be creating these states in the contracts/src/main/java/com/applestamp/states/
directory in this tutorial. Refer to the TemplateState.java
file in this directory to see a template state.
Learning objectives
After you have completed this tutorial, you will know how to create and implement states in a CorDapp.
Before you start
Before you start building states, read Key concepts: States.
Clone the CorDapp template repo
The easiest way to write any CorDapp is to start from a template. This ensures that you have the correct files to begin building.
- Navigate to the Kotlin and Java template repositories and decide which you’d like to clone:
Open a terminal window in the directory where you want to download the CorDapp template.
Run the following command:
git clone https://github.com/corda/cordapp-template-kotlin.git
git clone https://github.com/corda/cordapp-template-java.git
After you have cloned the repository you wish to use, navigate to the correct subdirectory:
cd cordapp-template-kotlin
cd cordapp-template-java
After you clone the CorDapp template, open the
cordapp-template-kotlin
orcordapp-template-java
in IntelliJ IDEA. If you don’t know how to open a CorDapp in IntelliJ, see the documentation on Running a sample CorDapp.Rename the package to
tutorial
. This changes all instances of thetemplate
package in the project totutorial
. In the drop-down menu that appears, select Rename module.
tutorial
because it is the tutorial CorDapp. When building your own CorDapp, name the package appropriately for your use case. This can be the same name as the CorDapp itself.Create the AppleStamp
state
First create the AppleStamp
state. This state is the voucher issued to customers.
Right-click the states folder, select New > Java Class and create a file called
AppleStamp
.Open the file.
Add annotations
The first thing you should do when writing a state is add the @BelongsToContract
annotation. This annotation establishes the relationship between a state and a contract. Without this, your state does not know which contract is used to verify it.
If you’ve copied in the template state, change the
TemplateContract.class
toAppleStampContract.class
.Add the annotation
@BelongsToContract(AppleStampContract.class)
to your state.
This what your code should look like so far:
@BelongsToContract(AppleStampContract.class)
AppleStampContract
yet. Ignore this error for now - you will add the contract class in the Write the contract tutorial.When naming your CorDapp files, it’s best practice to match your contract and state names. In this case the state is called AppleStamp
, so the contract is called AppleStampContract
. Follow this naming convention when you write an original CorDapp to avoid confusion.
Implement the state
The next line of code you add defines the type of ContractState you implement with the AppleStamp
class. Add this line to ensure that Corda recognizes the AppleStamp
as a state.
In this case, use a LinearState
to tie the AppleStamp
to a LinearID
.
Add the public class AppleStamp
implementing a LinearState
.
This is what your code should look like now:
@BelongsToContract(AppleStampContract.class)
public class AppleStamp implements LinearState {
Add private variables
Next, add the private variables for the stamp description (stampDesc
), the issuer of the stamp (issuer
), and the current owner of the stamp (holder
).
After adding these variables, your code should look like this:
@BelongsToContract(AppleStampContract.class)
public class AppleStamp implements LinearState {
//Private Variables
private String stampDesc; //For example: "One stamp can be exchanged for a basket of Gala apples."
private Party issuer; //The person who issued the stamp.
private Party holder; //The person who currently owns the stamp.
}
Add required variables and parameters
- All
LinearState
s must have a variable for the state’s linear ID. Add this variable under the private variables:
private UniqueIdentifier linearID;
- All Corda states must include a parameter to indicate the parties that store the states. Add this parameter below the
LinearStare
variable:
private List<AbstractParty> participants;
After adding these sections, your code should look like this:
@BelongsToContract(AppleStampContract.class)
public class AppleStamp implements LinearState {
//Private Variables
private String stampDesc; //For example: "One stamp can exchange for a basket of HoneyCrispy Apple"
private Party issuer; //The person who issued the stamp
private Party holder; //The person who currently owns the stamp
//LinearState required variable.
private UniqueIdentifier linearID;
//Parameter required by all Corda states to indicate storing parties
private List<AbstractParty> participants;
}
Add the constructor
Add a constructor to initialize the objects in the AppleStamp
state.
If you’re using IntelliJ, you can generate the constructor with a shortcut.
On macOS, press Command + N.
On Windows, press Alt + Insert.
Select Constructors in the Generate menu.
Select all the constructors that appear and click OK.
Add the
@ConstructorForDeserialization
annotation before the constructor to ensure that all variables appear.This annotation:
- Indicates which constructor is used for serialization when there are multiple constructors in a state class.
- Is usually annotated at the constructor that has the most parameters fields.
After adding the constructor, your code should look like this:
@BelongsToContract(AppleStampContract.class)
public class AppleStamp implements LinearState {
//Private Variables
private String stampDesc; //For example: "One stamp can exchange for a basket of HoneyCrispy Apple"
private Party issuer; //The person who issued the stamp
private Party holder; //The person who currently owns the stamp
//LinearState required variable.
private UniqueIdentifier linearID;
//ALL Corda States must have this parameter to indicate storing parties.
private List<AbstractParty> participants;
//Constructor Tips: Command + N in IntelliJ can auto generate constructor.
@ConstructorForDeserialization
public AppleStamp(String stampDesc, Party issuer, Party holder, UniqueIdentifier linearID) {
this.stampDesc = stampDesc;
this.issuer = issuer;
this.holder = holder;
this.linearID = linearID;
this.participants = new ArrayList<AbstractParty>();
this.participants.add(issuer);
this.participants.add(holder);
}
Add getters
To access a private variable outside of its class in Java, you must use a getter. If you do not use getters, your Corda node cannot pick up the variables.
Add a getter for each variable. After you’ve added the getters, your code should look like this:
@BelongsToContract(AppleStampContract.class)
public class AppleStamp implements LinearState {
//Private Variables
private String stampDesc; //For example: "One stamp can exchange for a basket of HoneyCrispy Apple"
private Party issuer; //The person who issued the stamp
private Party holder; //The person who currently owns the stamp
//LinearState required variable.
private UniqueIdentifier linearID;
//ALL Corda State required parameter to indicate storing parties
private List<AbstractParty> participants;
//Constructor Tips: Command + N in IntelliJ can auto generate constructor.
@ConstructorForDeserialization
public AppleStamp(String stampDesc, Party issuer, Party holder, UniqueIdentifier linearID) {
this.stampDesc = stampDesc;
this.issuer = issuer;
this.holder = holder;
this.linearID = linearID;
this.participants = new ArrayList<AbstractParty>();
this.participants.add(issuer);
this.participants.add(holder);
}
@NotNull
@Override
public List<AbstractParty> getParticipants() {
return this.participants;
}
@NotNull
@Override
public UniqueIdentifier getLinearId() {
return this.linearID;
}
//Getters
public String getStampDesc() {
return stampDesc;
}
public Party getIssuer() {
return issuer;
}
public Party getHolder() {
return holder;
}
}
Add imports
If you’re using IntelliJ or another IDE, the IDE automatically adds the imports you need.
IntelliJ indicates that an import is missing with red text. To add the import:
Click the red text. A message appears: “Unresolvable reference: {name of the missing input}”.
On macOS, press Option + Enter to automatically import that variable.
On Windows, press Alt + Enter to automatically import that variable.
Repeat this process with all missing imports.
Once you have added all imports, your code should look like this:
package com.tutorial.states;
import com.tutorial.contracts.AppleStampContract;
import net.corda.core.contracts.BelongsToContract;
import net.corda.core.contracts.LinearState;
import net.corda.core.contracts.UniqueIdentifier;
import net.corda.core.identity.AbstractParty;
import net.corda.core.identity.Party;
import net.corda.core.serialization.ConstructorForDeserialization;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
import java.util.List;
@BelongsToContract(AppleStampContract.class)
public class AppleStamp implements LinearState {
//Private Variables
private String stampDesc; //For example: "One stamp can exchange for a basket of HoneyCrispy Apple"
private Party issuer; //The person who issued the stamp
private Party holder; //The person who currently owns the stamp
//LinearState required variable.
private UniqueIdentifier linearID;
//ALL Corda State required parameter to indicate storing parties
private List<AbstractParty> participants;
//Constructor Tips: Command + N in IntelliJ can auto generate constructor.
@ConstructorForDeserialization
public AppleStamp(String stampDesc, Party issuer, Party holder, UniqueIdentifier linearID) {
this.stampDesc = stampDesc;
this.issuer = issuer;
this.holder = holder;
this.linearID = linearID;
this.participants = new ArrayList<AbstractParty>();
this.participants.add(issuer);
this.participants.add(holder);
}
@NotNull
@Override
public List<AbstractParty> getParticipants() {
return this.participants;
}
@NotNull
@Override
public UniqueIdentifier getLinearId() {
return this.linearID;
}
//Getters
public String getStampDesc() {
return stampDesc;
}
public Party getIssuer() {
return issuer;
}
public Party getHolder() {
return holder;
}
}
Create the BasketOfApples
state
The BasketOfApples
state is the basket of apples that Farmer Bob self-issues to prepare the apples for Peter. Now that you’ve written your first state, try writing the BasketOfApples
state using the following information.
Private variables:
description
- The brand or type of apple. Use typeString
.farm
- The origin of the apples. Use typeParty
.owner
- The person exchanging the basket of apples for the voucher (Farmer Bob). Use typeParty
.weight
- The weight of the basket of apples. Use typeint
.
The BasketOfApples
state is involved in two transactions. In the first transaction, Farmer Bob self-issues the BasketOfApples
. The Farm
party then fills both the owner
and farm
fields of the transaction. You could compact the transaction to carry only these parameters: public BasketOfApples(String description, Party farm, int weight) {}
If you are writing in Java, when you have multiple constructors in one state class, you must annotate which constructor is the base for serialization. This constructor will most likely carry all relevant information for the state. For example, the constructor public BasketOfApples(String description, Party farm, int weight) {}
), does not have an owner
field. You must create another constructor that has all fields, and annotate this constructor with @ConstructorForDeserialization
.
Check your work
Once you’ve written the BasketOfApples
state, check your code against the sample below. Your code should look something like this:
package com.tutorial.states;
import com.tutorial.contracts.BasketOfApplesContract;
import net.corda.core.contracts.BelongsToContract;
import net.corda.core.contracts.ContractState;
import net.corda.core.identity.AbstractParty;
import net.corda.core.identity.Party;
import net.corda.core.serialization.ConstructorForDeserialization;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
import java.util.List;
@BelongsToContract(BasketOfApplesContract.class)
public class BasketOfApples implements ContractState {
//Private Variables
private String description; //Brand or type
private Party farm; //Origin of the apple
private Party owner; //The person who exchange the basket of apple with the stamp.
private int weight;
//ALL Corda State required parameter to indicate storing parties
private List<AbstractParty> participants;
//Constructors
//Basket of Apple creation. Only farm name is stored.
public BasketOfApples(String description, Party farm, int weight) {
this.description = description;
this.farm = farm;
this.owner=farm;
this.weight = weight;
this.participants = new ArrayList<AbstractParty>();
this.participants.add(farm);
}
//Constructor for object creation during transaction
@ConstructorForDeserialization
public BasketOfApples(String description, Party farm, Party owner, int weight) {
this.description = description;
this.farm = farm;
this.owner = owner;
this.weight = weight;
this.participants = new ArrayList<AbstractParty>();
this.participants.add(farm);
this.participants.add(owner);
}
@NotNull
@Override
public List<AbstractParty> getParticipants() {
return participants;
}
//getters
public String getDescription() {
return description;
}
public Party getFarm() {
return farm;
}
public Party getOwner() {
return owner;
}
public int getWeight() {
return weight;
}
public BasketOfApples changeOwner(Party buyer){
BasketOfApples newOwnerState = new BasketOfApples(this.description,this.farm,buyer,this.weight);
return newOwnerState;
}
}
Next steps
Follow the Write the contracts tutorial to continue on this learning path.
Related content
Was this page helpful?
Thanks for your feedback!
Chat with us
Chat with us on our #docs channel on slack. You can also join a lot of other slack channels there and have access to 1-on-1 communication with members of the R3 team and the online community.
Propose documentation improvements directly
Help us to improve the docs by contributing directly. It's simple - just fork this repository and raise a PR of your own - R3's Technical Writers will review it and apply the relevant suggestions.
We're sorry this page wasn't helpful. Let us know how we can make it better!
Chat with us
Chat with us on our #docs channel on slack. You can also join a lot of other slack channels there and have access to 1-on-1 communication with members of the R3 team and the online community.
Create an issue
Create a new GitHub issue in this repository - submit technical feedback, draw attention to a potential documentation bug, or share ideas for improvement and general feedback.
Propose documentation improvements directly
Help us to improve the docs by contributing directly. It's simple - just fork this repository and raise a PR of your own - R3's Technical Writers will review it and apply the relevant suggestions.