Tokens represent any kind of asset on your network. Token SDK provides the easiest way for you to create them.
This blog is mainly about my coding experience related to my designed ConcertTicketBooking system. As a recap, my design of the app is shown below. If you are not familiar with the logic design, you can refer to this blog for more details.
Correspondingly, I’ve divided the article into three main sections — writing the state, writing the contract, and writing the workflow.
Writing the state:
Our ticket booking system already has four defined states — Ticket State, VenueOwnership State, Money State, and Request State. In order to code those states, we should define their types and their contained parameters.
An item’s type usually tells us about its basic properties. In Corda, we can have OwnableState, LinearState, QueryState, and so on.
We would like to have our asset presented in a Token format using the TokenSDK. In the TicketBookingSystem example, Ticket, VenueOwnership, and Money have actual value in the transaction and can be seen as assets, hence we are defining these states as Tokentypes. The Request doesn’t stand for any asset, but each request is unique so it can be seen as a LinearState instance.
For the TokenTypes, whether it is fungible or not depends on the specific use case. In this example, my design is each person who wants to see the concert must book the ticket by himself/herself and can only book once. This means the Ticket can only have one owner and can’t be shared. It works the same for the VenueOwnership, which can only be lensed to one agency at a time. Since the Ticket state and the VenueOwnership state are already the smallest unit of the asset, hence we make them Non-Fungible Tokens. But for the Money, I just assume it’s from the same issuer and its smallest unit is of the same value, hence the Money can be defined as a Fungible Token.
To define the contained parameters:
As for the VenueOwnershipState
, the concertID
differentiates each lease; the hallID specifies which concert hall is being leased; the issuer is usually the venue holder; the contractor specifies who will this concert hall be rented to; the description is about what event is going to be held here; the startTime and endTime defines when does lease start and end; the maxSeat
tells what the maximum number of people is expected to come; the soldOut
records the number of seats that have been sold; the price of each seat is also defined in this class.
VenueOwnershipState |
concertID: String hallID: String startTime: String endTime: String issuer: Party contractor: Party description: String maxSeat: Int soldOut: Int price: Amount<Currency> |
For the Ticket state, it defines the parameters for the ticket details. So there’s no doubt that each ticket has a startTime
, an endTime with the hallID. That information reminds the customer of the exact place and time of the concert. The ticket also needs to contain the name of the issuer, the agency, and the buyer. Therefore, when the venue staffs check the ticket, they will know that this ticket is issued by them and can confirm its validity.
TicketState |
startTime: String endTime: String concertID: String issuer: Party agency: Party buyer: Party |
For the Request state, it has the concertID
that points to the specific performance, the name of the buyer, and the receiver who is in charge of tackling the customer’s ticket request (usually the Agency).
RequestState |
requester: Party receiver: Party concertID: String |
For the Money state, we use the FiatCurrency
class that is pre-defined in R3’s library.
With the following class diagram, we will grab a clearer overview of what’s the relations among states.
Writing the contract:
The rules in the contract help check whether each state meets the business logic requirement.
Each defined state belongs to a contract and needs to be annotated using @BelongsToContract
annotation. Within the contract, the details of each state are clearly listed during the transaction.
For the contract of the VenueOwnership. When created, there must have a VenueOwnershipState as its output. In that state, the start time should be later than the current time, the end time must be later than the start time. The soldOut number is less than the maxSeat
. The issuer is not the same as the contractor.
For the contract of the TicketState, the limitation is the end time is later than the start time when creating the ticket.
Writing the workflow:
Corda’s
@InitiatingFlow
annotation enables a node to initiate a communication with a counterparty and request its counterparty to start its side of the flow communication.
we call the starting flow the InitiatedFlow. Its counterparty generates the ResponderFlow. The @InitiatedBy annotation differentiates the ResponderFlow from the InitiatedFlow. Those two flows are essential for a complete SignedTransaction.
After knowing the definition of the InitiatedFlow and the ResponderFlow, we are going to complete the workflows step by step:
- IssueCurrency flow: The Bank issues money, we can refer to the
‘
dollartohouse
’
instance to build this flow.
- Generally, this implementation is about creating the FungibleToken instance and then issuing it.
- This IssueCurrency flow initiated by the Bank node takes three inputs: the currency’s type (whether it’s GBP or USB), the amount value, and the receiver. Those three inputs help define the properties of the FungibleToken for the fiat currency to be issued.
- We can utilize the
IssueTokens() flow
in Corda’s library to complete the issuing process. Since theIssueTokens() flow
returns a complete transaction(SignedTransaction), there’s no need for us to write a ResponderFlow by ourselves.
2. VenueSale flow: The VenueHolder sells the venue’s ownership to the Agency and charges it. The selling process is all about creating, issuing, and transferring the VenueOwnership.
- In the implementation, the VenueHolder node initiates the VenueSale flow, which takes each concert’s basic information as inputs and wraps it into a new VenueOwnershipState.
- In this InitiatedFlow, we use CreateEvolvableTokens() flow to add VenueOwnershipState (EvolvableTokenType) onto the ledger.
- Then we clarify the ownership is a NonFungibleToken and issue it. But the problem is the NonFungibleToken’s constructor only accepts a TokenType rather than a LinearState. To solve this, we use
toPointer()
function to convert the VenueOwnership from a ContractState type to the TokenType. We still useIssueTokens()
function to issue this token piece. - Before transferring the ownership, the VenueHolder is expected to charge the rental fee from the Agency (counterparty). To achieve this, we use the initiateFlow() and sendSession() function to inform the Agency of the 7836 5158payment amount. And the InitiatedFlow will keep waiting if it doesn’t get any response from the ResponderFlow. One thing to note here: In this use case, the rent fee is calculated by the maxSeat*fixed selling price per seat/2.
- The ResponderFlow with the @InitiatedBy annotation is invoked automatically by the InitiateFlow() function in the InitiatedFlow. In this ResponderFlow, the Agency sends the ReferencedState of its FiatCurrency back to the VenueHolder by calling SendStateAndRefFlow(). This subflow not only activates the blocked InitiatedFlow by calling the
ReceiveStateAndRefFlow<FungibleToken>()
, but also provides the VenueHolder with the right to move those FiatCurrency from the Agency’s account to its own account usingaddMoveNonFungibleTokens()
function. - After that, the contract with movable FiatCurrency (Agency -> VenueHolder using addMoveTokens() function) is added with VenueOwnershipState (VenueHolder -> Agency using addMoveNonFungibleTokens() function) are sent by the VenueHolder through the InitiatedFlow. The ResponderFlow will automatically sign after passing the
checkTransaction()
function. Both sides reach a deal!
3. RequestTicket flow: The Buyer then sends the ticket request and money to the Agency. This request specifies the concertID
the Buyer wants to book. Then the Agency checks the availability of seats for that concert, makes the decision, and updates this transaction to the VenueHolder.
- Firstly, the Buyer node initiates the RequestTicket flow. We pair the currency type with the amount and wrap the
concertID
in the RequestState. - Then the Money and this RequestState are respectively sent to the Agency using SendStateAndRefFlow flow and the session.send() function.
- In the ResponderFlow, the Agency firstly checks the seat’s availability for the specific concert using
filter()
function. If there’re seats left, it will accept the payment using the addMoveTokens() function. Then it appends the RequestState to the contract and sends it back to the Buyer as a receipt for the payment. - The contract gotten from the Agency is signed automatically by the Buyer using an inner class
SignTx
that contains thecheckTransaction()
function. - Meanwhile, the transaction ID of this receipt is recorded by the Agency so that this transaction can be updated to the VenueHolder using the ReportManuallyFlow (referenced the observableStates sample).
4. IssueTicket flow: Finally, the VenueHolder checks the reported the RequestState
in its vault and uses the RequestID to issue the ticket to the Buyer. Also, it updates the VenueOwnership’s seat information.
- The requester’s name and the
concertID
of the new TicketState are gotten by querying the RequestState’s RequestID. Moreover, the new TicketState’s startTime and endTime are gotten by querying theVenueOwnershipState’s
using theconcertID
. - Then like what we’ve done to the VenueSale flow, we issue this new TicketState as Non-FungibleTokens. One thing that is different from the VenueSale flow is that in this transaction, there’s no need for the VenueHolder to charge the Buyer, so it can issue the ticket to the Buyer directly using
IssueTokens
withoutaddMoveNonFungibleTokens()
function. - The final step is to update the information of the
VenueOwnershipState
.
The above-mentioned sections are all about my journey of turning a design into a real application. From my perspective, writing the flow is the most difficult part of the whole process, but the open-sourced sample code really helps me a lot when I got stuck. I hope my explanation of codes and use of functions can somewhat contribute to your own work!