Enhanced Transfers

In this tutorial, we delve deeper into the concepts that were introduced in our getting-started tutorials. In particular, we will extend on the transfer tutorial.

We begin by understanding the simple form of settlement. It transpires when a customer’s funds are transferred to another account within the same bank. Consequently, the sender’s account is debited (balance decreases), and the recipient’s account is credited (balance increases). This process is internally managed within the bank’s systems and usually occurs instantly, as it doesn’t require interaction with external systems or institutions.

In Daml Finance, such fund transfers are not necessarily represented by a settlement workflow that involves allocating and approving instructions. Instead, a “direct” transfer of funds can occur between two parties, such as Alice and Bob. This transfer debits the sending account and atomically credits the receiving account.

Next, we will explore how to configure the controllers responsible for authorizing incoming transfers (credits) and outgoing transfers (debits) of holdings to an account.

Configuring Account Controllers

The Controllers data type specifies the parties that need to authorize incoming and outgoing transfers to an account.

For this tutorial, we provide four example scripts illustrating various incoming and outgoing controller settings:

Script Incoming Controllers Outgoing Controllers

runDualControlTransfer

runDiscretionaryTransfer

runSovereignTransfer

runUnilateralTransfer

Anyone

Custodian

Owner

Anyone

Both (owner and custodian)

Custodian

Owner

Owner

Each script begins by running a setup script runSetupTransferRequestWith that requests a transfer of a holding from Alice to Bob at the Bank. The setup script takes a configuration as input to set up Alice’s and Bob’s account controllers, as outlined in the table above.

The last step of the setup script creates a transfer request of a holding from Alice to Bob:

  let
    transferRequest = Transfer.Request with
      requestor
      receiverAccount = bobAccount
      transferableCid = aliceHoldingCid
      accepted = S.fromList []
      observers = S.fromList [alice, bob, bank]
  transferRequestCid <- submit requestor do createCmd transferRequest

The transfer Request template is designed for the stepwise collection of the necessary authorizations for transferring a holding to a new owner:

template Request
  with
    requestor : Party
      -- ^ The requestor.
    receiverAccount : AccountKey
      -- ^ The account to which the holding is sent.
    transferableCid : ContractId Transferable.I
      -- ^ The holding instance to be sent.
    accepted : Set Party
      -- ^ Current set of parties that accept the transfer.
    observers : Set Party
      -- ^ Observers.
  where
    signatory requestor, accepted
    observer observers

    choice Accept : ContractId Request
      with
        actors : Set Party
      controller actors
      do
        create this with accepted = actors `union` this.accepted

    choice Effectuate : ContractId Transferable.I
      with
        actors : Set Party
      controller actors
      do
        exercise transferableCid Transferable.Transfer with
          actors = actors `union` this.accepted
          newOwnerAccount = receiverAccount

Dual Control

In the runDualControlTransfer script, both the custodian and the owner of an account must authorize outgoing transfers (debits), while incoming transfers (credits) require no authorization.

This script begins by setting up accounts accordingly and creating a transfer request instance:

  let
    dualControl = AccountControllers
        with
          incoming = Anyone
          outgoing = Both
  SetupTransferRequest{bank; alice; bob; requestor; transferRequestCid} <-
    runSetupTransferRequestWith dualControl

To execute the transfer, both the Bank and Alice must authorize:

  transferRequestCid <- submit bank do
    exerciseCmd transferRequestCid Transfer.Accept with actors = S.singleton bank
  submit alice do
    exerciseCmd transferRequestCid Transfer.Effectuate with actors = S.singleton alice

Discretionary

The runDiscretionaryTransfer script specifies that the custodian controls both incoming and outgoing transfers:

  let
    discretionary = AccountControllers
      with
        incoming = Custodian
        outgoing = Custodian
  setupState@SetupTransferRequest{bank, alice, bob, requestor, transferRequestCid} <-
    runSetupTransferRequestWith discretionary

Following the setup, the Bank can execute the transfer single-handedly:

  submit bank do
    exerciseCmd transferRequestCid Transfer.Effectuate with actors = S.singleton bank

Sovereign

In the runSovereignTransfer script, the owner controls both incoming and outgoing transfers:

  let
    sovereign = AccountControllers
      with
        incoming = Owner
        outgoing = Owner
  SetupTransferRequest{bank; alice; bob; requestor; transferRequestCid} <-
    runSetupTransferRequestWith sovereign

As Alice is the outgoing controller of the sending account, and Bob is the incoming controller of the receiving account, both need to authorize the transfer:

  transferRequestCid <- submit bob do
    exerciseCmd transferRequestCid Transfer.Accept with actors = S.singleton bob
  submit alice do
    exerciseCmd transferRequestCid Transfer.Effectuate with actors = S.singleton alice

Unilateral

In our final example script, runUnilateralTransfer, the owner controls outgoing transfers, while incoming transfers require no additional authorization:

  let
    unilateral = AccountControllers
      with
        incoming = Anyone
        outgoing = Owner
  SetupTransferRequest{bank; alice; bob; requestor; transferRequestCid} <-
    runSetupTransferRequestWith unilateral

Once the setup is complete, Alice can independently execute the transfer to Bob:

  transferRequestCid <- submit alice do
    exerciseCmd transferRequestCid Transfer.Effectuate with actors = S.singleton alice

Summary

By now, you should understand how to configure incoming and outgoing controllers for accounts based on your requirements. Key concepts to remember include:

  • To execute a transfer between a sender and a receiver, the outgoing controllers of the sending account and the incoming controllers of the receiving account need to authorize it.
  • The required authorization can be provided by a generalized propose-accept template, which allows more than one party to accept.

Ownership transfers usually occur as part of a larger financial transaction. The next tutorials will guide you on how to create such a transaction and how to settle it atomically.