Implementing Transfer of a Fungible Asset in Corda
March 24, 2020
Extending the Corda Bootcamp Token CorDapp
Have you attended one of our Corda Bootcamps? If so, you would have had a day very well spent learning the basic concepts of Corda with some of our very talented Corda Evangelists. You would have learned to develop, deploy and run a working CorDapp by the end of the day. An exciting experience isn’t it?
But the Token CorDapp we built during the bootcamp is not complete just yet. All we did was issue the token. What if you want to transfer the issued token to some other counterparty? The goal of the bootcamp was to give you an idea of how to develop the CorDapp and thus we left the transfer function for you to experiment with and complete as homework.
If you have not been to one of our in-person Corda Bootcamps, check out this condensed version which we recently hosted online.
With the knowledge from the bootcamp, I am sure most of you were able to get it right. But just in case you are stuck somewhere, I am here to help.
Understanding Token Transfer Transaction
So let’s start where we left off. We have been able to successfully issue a token. Here’s our issue transaction:
As per the transaction above PartyA
(issuer) issued 100 tokens to PartyC
(owner). Now let’s take a look at how our token transfer transaction would look like. Consider PartyC
(owner) wants to transfer the tokens to PartyB
.
Unlike the issue transaction, we have an input in the transfer transaction, since we are trying to update the state (change owner) of an existing token on the ledger. Thus on completion of the transaction, the existing token state (taken as input to the transaction) would be consumed and a new token state would be produced with the new owner.
All good till now. But what if PartyA wanted to transfer only some of the token not all of them, remember token is a fungible asset.
A Fungible asset is something that can be split and merged. Example: Currency. We can split $10 into two $5, similarly two $5 can be merged into a single $10.
Transfer of Fungible Asset
In the case of a fungible asset, the remaining change should be returned to the owner. Thus if the Party C (owner) holds 100 tokens and he wished to transfer 60 of them to Party B. Then Party C should be left with 40 tokens on completion of the transaction. But we can’t split the input state, it would be consumed as a whole.
Thus we can achieve this by creating two output states:
- One for the 60 tokens to be transferred to PartyB
- The second one for the change of 40 tokens to be retained by PartyC
Let’s re-visualize the transaction.
Revisiting the Token CorDapp
Now that we have a good idea of what we are trying to accomplish. Let’s revisit the Token CorDapp and try to implement the Transfer functionality.
Here’s the source code: https://github.com/corda/bootcamp-cordapp
In case you haven’t been to one of our Corda Bootcamps or haven’t gotten the solutions right, you can find it here:
https://github.com/corda/bootcamp-cordapp/blob/v4/Solutions.md
TokenState
Let’s revisit the TokenState
. We already have the TokenState
implemented with the issuer, owner and amount properties. That’s all we are going to need. So, no need to change anything in the TokenState
.
You may consider adding an identifier if you wish to issue different types of tokens, but for simplicity, I will leave it as is.
TokenContract
Let’s take a look at the TokenContract
.
Notice that the verify
method only has validation code for the token issuance. We would need to add some validation for the token transfer as well.
And in order to differentiate whether we are doing an issuance or a transfer, we need a new command i.e Transfer
command.
Now, let’s think of a few validations that we want to do as part of the token transfer.
- As we would be consuming something from the ledger, we need to validate that the number of inputs is greater than zero.
- The number of outputs should be either one or two. One, if there is no change to be returned, and two if we have any change to be returned to the owner.
- We need to validate that the total number of tokens in the input should be equal to the total tokens in the output. A mismatch would result in the loss of integrity of the ledger.
- We would want the owner of the token to sign the transaction.
Let’s move ahead with these. You can add more validations if you wish. But let’s keep it short for simplicity.
We might want to have separate verify
methods each for Issue and Transfer to make to code more readable. Below is the code for our new TokenContract.
TokenTransferFlow
Now that we have got the State and Contract in place, it’s time to implement our TokenTransferFlow
. It is very much similar to what we did when we issued the token. One extra thing we need to do here is to provide the input to the TransactionBuilder
.
Constructor parameters
Before that, we want to identify how we differentiate between the token. We don’t want to mix up the tokens issued by different issuers (As they are not the same. A dollar issued by Canadian central bank is not equal to a dollar issued by US central bank). So while we implement the Transfer flow we may want to take the issuer
as an input from the user. Other things we need as input are the <amount
of token to transfer and the receiver
.
private final Party issuer;
private final int amount;
private final Party receiver;
public Initiator(Party issuer, int amount, Party receiver) {
this.issuer = issuer;
this.amount = amount;
this.receiver = receiver;
}
Querying the vault to fetch the input
Now coming back to the input of the transaction. All the ContractState issued in the ledger are store in the Vault. So we need to fetch the TokenState
from the vault and use it as an input to the transaction.
Here is how we fetch the TokenState:
List<StateAndRef> allTokenStateAndRefs = getServiceHub().getVaultService().queryBy(TokenState.class).getStates();
We use VaultService
to fetch all TokenState
. This would by default return all UNCONSUMED
tokens. But we just want tokens that are issued by a particular party i.e. the issuer
passed in the constructor parameter. So let’s filter down the list.
But, we can’t just put all of the tokens issued by our issuer as the input, we just need the number of tokens requested to be transferred. Also, we need to validate that there is enough token available to spend. We can’t transfer the required amount of token’s if it isn’t available. So let’s do that:
One last thing, we also need to determine the change we need to return to the owner.
Huh!! That’s it for the inputs. It’s going to be simple from here on, I promise!!
Building the Transaction.
Now, let’s start building the transaction.
- The first step towards that is to fetch the notary.
Party notary = getServiceHub().getNetworkMapCache()
.getNotaryIdentities().get(0)
You might need to fetch the notary from the inputState in production ready corDapps, to make sure that you are using the same notary that is used when the asset was issued. But since this is a demo app and we would only has one notary in the demo network, we can use it directly to avoid complexity.
2. Next, create an instance of the TransactionBuilder
TransactionBuilder txBuilder = new TransactionBuilder(notary);
3. Add the inputs we filtered earlier.
inputStateAndRef.forEach(txBuilder::addInputState);
4. Add the outputs
TokenState outputState = new TokenState( issuer, receiver, amount);
txBuilder.addOutputState(outputState)
// Add the change if its greater than zero
if(change.get() > 0){
TokenState changeState =
new TokenState(issuer, getOurIdentity(), change.get());
txBuilder.addOutputState(changeState);
}
5. Finally, add the command.
txBuilder.addCommand(new TokenContract.Commands.Transfer(), ImmutableList.of(getOurIdentity().getOwningKey()));
So, let’s put this all together:
Verify, Sign and Notarize the transaction
This is just what we did with the issuance, nothing changes here, notice we do not need to collect any counterparty signatures, as for this we have assumed that just the token owner’s signature is enough to complete this transaction.
So there you go, we are done. Let’s take a look at the final TokenTransferFlow class:
Hopefully, this gives a very good idea of handling transfer/move transactions in Corda. If you face any issues with Corda, feel free to post a question on Stack Overflow: https://stackoverflow.com/questions/tagged/corda.
Thanks for reading through, hopefully, you like this post. Want to learn more and connect with other CorDapp developers? You may consider joining us in our public slack channel. Learn more about Corda at https://corda.net.
— Ashutosh Meher, Developer Evangelist at R3
Twitter: @iashutoshmeher, LinkedIn: @iashutoshmeher
Implementing Transfer of a Fungible Asset in Corda was originally published in Corda on Medium, where people are continuing the conversation by highlighting and responding to this story.