Transfer

This tutorial builds on the previous chapter, which introduced account, instrument, and holding.

We are now going to transfer the holding that we created in the previous tutorial from Alice to Bob.

Run the Script

Let us now explore the Transfer script step-by-step. It builds on the previous Holdings tutorial script in the sense that the same accounts and the existing holdings are used.

Transfer Cash from Alice to Bob

The final step of our Setup script transfers Alice’s holding to Bob using the Transfer workflow. In our tutorial example, the receiver of the cash makes the transfer request:

  transferRequestCid <- submit bob do
    createCmd Transfer.Request with
      receiverAccount = bobAccount
      instrument = cashInstrument
      amount = 1000.0
      currentOwner = alice

  bobCashHoldingCid <- submit alice do
    exerciseCmd transferRequestCid Transfer.Accept with holdingCid = aliceCashHoldingCid

Bob requests the cash to be transferred to his account. Alice then accepts the request.

You notice that here we make explicit use of the fact that Alice can readAs the public party. This is needed as, in order to complete the transfer, visibility on the receiving account’s holding factory is required.

Frequently Asked Questions

How does the Transfer workflow work?

If you look at the implementation of the Transfer workflow, you will notice the following lines:

        let transferableCid = coerceInterfaceContractId @Transferable.I holdingCid

        newTransferableCid <- exercise transferableCid Transferable.Transfer with
          actors = fromList [currentOwner, receiverAccount.owner]
          newOwnerAccount = receiverAccount

        pure $ toInterfaceContractId @Holding.I newTransferableCid

The first line converts the holding contract id (of type ContractId Holding.I) to the Transferable.I interface using coerceInterfaceContractId.

Then, the Transfer choice, defined as part of the Transferable interface, is exercised.

Finally, the new holding is converted back to a Holding.I before it is returned. This is done using toInterfaceContractId.

In order to fully understand these instructions, we need to keep in mind the interface hierarchy used by our holding implementation.

A diagram of the interface hierarchy. From left to right, Disclosure, Lockable, Holding, and Transferable are each linked by arrows pointing left. Additionally, Fungible is located above from Transferable, and has an arrow pointing to Holding. All arrows are labeled "requires".

We use coerceInterfaceContractId to convert the Holding.I to a Transferable. The success of this operation is not guaranteed and will result in a run-time error if the holding implementation at hand does not implement Transferable.

We use toInterfaceContractId to convert back to a Holding. This is because all Transferables implement the Holding.I interface, so the validity of this operation is guaranteed at compile-time.

Why is Alice an observer on Bob’s account?

You might have noticed that Alice is an observer of Bob’s account and you might be wondering why this is the case.

This is because the party exercising the Transfer choice, which in this case is Alice, needs to fetch Bob’s account in order to verify that it has not been archived.

If we wanted to avoid Bob’s account contract ever being disclosed to Alice, we would need a third party (in this case the Bank) to execute the Transfer.

Exercises

There are a couple of improvements to the code that can be implemented as an exercise. They will help you familiarize yourself with the library and with Daml interfaces.

Split the Holding to Transfer the Right Amount

In the example, Bob requests USD 1000 from Alice and Alice allocates a holding for exactly the right amount, because the transfer would otherwise fail. We want the transfer to be successful also if Alice allocates a holding for a larger amount e.g., USD 1500.

We can leverage the fact that the holding implements the Fungible interface, which makes it possible to Split it into a holding of USD 1000 and one of USD 500. In the implementation of the CashTransferRequest_Accept choice:

  • cast the allocated holding to the Fungible interface
  • use the Split choice to split the larger holding into two holdings
  • execute the transfer, allocating the holding with the correct amount

In the last step, you will need to cast the Fungible to a Transferable using toInterfaceContractId.

Temporary Account Disclosure

There is no reason for Alice to be an observer on Bob’s account before the transfer is initiated by Bob (and after the transfer is executed).

Modify the original code, such that:

  • Bob’s account is disclosed to Alice once the transfer is initiated
  • When the Transfer is executed, Alice removes herself from the account observers

In order to do that, you can leverage the fact that Account implements the Disclosure interface. This interface exposes the AddObservers and RemoveObservers choices, which can be used to disclose / undisclose Bob’s account contract to Alice. In order to exercise these choices, you can use the Account.exerciseInterfaceByKey utility function.

Summary

You now learned how to perform a simple transfer. The key concepts to take away are:

  • Holdings represent the ownership of a financial instrument at a custodian.
  • Transfers change ownership of a holding.

Ownership transfers typically happen as part of a larger financial transaction. The next tutorial will show you how to create such a transaction and how to settle it atomically.