Intermediated Lifecycling of an Instrument

This tutorial describes the lifecycling flow of an instrument with an intermediary party between the issuer and the investor. We will use the a Generic Instrument, but the same concepts apply to other instrument types as well.

We will illustrate the following steps:

  1. Creating a Generic Instrument modeling a fixed rate bond
  2. Defining an intermediated settlement route
  3. Defining a suitable lifecycle event
  4. Lifecycling the instrument
  5. Non-atomic settlement of the lifecycle effects
  6. Atomic settlement of the lifecycle effects

To follow the script used in this tutorial, you can clone the Daml Finance repository. In particular, the file Instrument/Generic/Test/Intermediated/BondCoupon.daml is the starting point of this tutorial. It contains an example for both non-atomic and atomic settlement of lifecycle effects. In this tutorial we will focus on the non-atomic settlement, but we will mention atomic settlement towards the end.

Create the Instrument

We start by using a Generic Instrument to model a fixed rate bond paying a 4% p.a. coupon with a 6M coupon period.

Define an Intermediated Settlement Route

In the case of intermediated lifecycling, we need to define a settlement route for the bond instrument, which depends on the account structure:

    {-
    Bond (security) account structure :

      Issuer
        |
        CSD
        |
      Investor
    -}
    let
      route =
        ( bondLabel
        , Hierarchy with
            rootCustodian = issuer
            pathsToRootCustodian = [[investor, csd]]
        )

Similarly, we define a settlement route for the cash instrument instrument:

      {-
        Cash account structure :

            Central Bank
            /    |     \
          CSD  Issuer  Bank
                        \
                      Investor
      -}
      route =
        ( label
        , Hierarchy with
            rootCustodian = centralBank
            pathsToRootCustodian = [[investor, bank], [csd], [issuer]]
        )

Define a Lifecycle Event

Since the bond pays a coupon twice a year, payment is a time-based event. The requirement to pay the coupon is governed by actual time. However, in a trading and settlement system, it is useful to be able to control the time variable, in order to simulate previous/future payments, or to have some flexibility regarding when to process events.

Because of this, the issuer defines a clock update event contract, which signals that a certain time has been reached:

  -- create clock update event
  clockEventCid <- createClockUpdateEvent (S.singleton issuer) today S.empty

Lifecycle the Bond Instrument

Using the Lifecycle interface, the CSD creates a lifecycle rule contract:

  -- Create a lifecycle rule
  lifecycleRuleCid <- toInterfaceContractId @Lifecycle.I <$> submit csd do
    createCmd Lifecycle.Rule with
      providers = S.singleton csd
      observers= M.empty
      lifecycler = issuer
      id = Id "LifecycleRule"
      description = "Rule to lifecycle a generic instrument"

The issuer of the bond is responsible for initiating the lifecycling of the coupon payment, by exercising the Evolve choice on the coupon date:

  -- Try to lifecycle the instrument
  (_, [effectCid]) <- submit issuer do
    exerciseCmd lifecycleRuleCid Lifecycle.Evolve with
      eventCid = clockEventCid
      observableCids = []
      instrument = bondInstrument

This internally uses the Event interface. In our case, the event is a clock update event, since the coupon payment is triggered by the passage of time.

The return type of effectCid is an Effect interface. It will contain the effect(s) of the lifecycling, in this case a coupon payment. If there is nothing to lifecycle, for example because there is no coupon to be paid today, this would be empty.

Non-atomic Settlement

In order to process the effect(s) of the lifecycling (in this case: pay the coupon), we need to create settlement instructions. In the non-atomic case, this is done in two steps.

First, there is the settlement between the issuer and the CSD. By using the EffectSettlementService template, the issuer can claim and settle the lifecycling effects in one step by exercising the ClaimAndSettle choice:

  -- Setup settlement contract between issuer and CSD
  -- In order for the workflow to be successful, we need to disclose the CSD's cash account to the
  -- Issuer.
  Account.submitExerciseInterfaceByKeyCmd @Disclosure.I [csd] [] csdCashAccount
    Disclosure.AddObservers with
      disclosers = S.singleton csd; observersToAdd = ("Issuer", S.singleton issuer)

  settle1Cid <- submitMulti [csd, issuer] [] do
    createCmd EffectSettlementService with
      csd
      issuer
      instrumentId = bondInstrument.id
      securitiesAccount = csdAccountAtIssuer
      issuerCashAccount
      csdCashAccount
      settlementRoutes = routes

  -- CSD claims and settles effect against issuer
  (effectCid, newInstrumentHoldingCid, [cashHolding]) <- submitMulti [issuer] [publicParty] do
    exerciseCmd settle1Cid ClaimAndSettle with
      instrumentHoldingCid = csdBondHoldingCid; cashHoldingCid = issuerCashHoldingCid; effectCid

Then, there is the settlement between the CSD and the investor. We start by creating a settlement factory:

  -- investor claims effect against CSD
  routeProviderCid <- toInterfaceContractId <$> submit csd do
    createCmd IntermediatedStatic with
      provider = csd; observers = S.singleton investor; paths = routes

  settlementFactoryCid <- submit csd do
    toInterfaceContractId <$> createCmd Factory with
      provider = csd; observers = S.singleton investor

Settlement instructions are created by using the Claim interface and exercising the ClaimEffect choice:

  lifecycleClaimRuleCid <- toInterfaceContractId @Claim.I <$> submit csd do
    createCmd Claim.Rule with
      providers = S.singleton csd
      claimers = S.fromList [csd, investor]
      settlers
      routeProviderCid
      settlementFactoryCid
      netInstructions = False

  result <- submit csd do
    exerciseCmd lifecycleClaimRuleCid Claim.ClaimEffect with
      claimer = csd
      holdingCids = [investorBondHoldingCid]
      effectCid
      batchId = Id "CouponSettlement"

Claiming the effect has two consequences:

  • the investor’s holding is upgraded to a new instrument version (where the coupon has been paid)
  • settlement instructions are generated in order to process the coupon payment

Finally, the settlement instructions are allocated, approved and then settled.

  let
    [investorBondInstructionCid, csdBondInstructionCid, csdCashInstructionCid,
      bankCashInstructionCid] = result.instructionCids

  -- Allocate instructions
  (investorBondInstructionCid, _) <- submit investor do
    exerciseCmd investorBondInstructionCid Instruction.Allocate with
      actors = S.singleton investor; allocation = Pledge investorBondHoldingCid
  (csdBondInstructionCid, _) <- submit csd do
    exerciseCmd csdBondInstructionCid Instruction.Allocate with
      actors = S.singleton csd; allocation = CreditReceiver
  (csdCashInstructionCid, _) <- submit csd do
    exerciseCmd csdCashInstructionCid Instruction.Allocate with
      actors = S.singleton csd; allocation = Pledge cashHolding
  (bankCashInstructionCid, _) <- submit bank do
    exerciseCmd bankCashInstructionCid Instruction.Allocate with
      actors = S.singleton bank; allocation = CreditReceiver

  -- Approve instructions
  investorBondInstructionCid <- submit csd do
    exerciseCmd investorBondInstructionCid Instruction.Approve with
      actors = S.singleton csd; approval = DebitSender
  csdBondInstructionCid <- submit investor do
    exerciseCmd csdBondInstructionCid Instruction.Approve with
      actors = S.singleton investor; approval = TakeDelivery investorSecuritiesAccount
  csdCashInstructionCid <- submit bank do
    exerciseCmd csdCashInstructionCid Instruction.Approve with
      actors = S.singleton bank; approval = TakeDelivery bankCashAccount
  bankCashInstructionCid <- submit investor do
    exerciseCmd bankCashInstructionCid Instruction.Approve with
      actors = S.singleton investor; approval = TakeDelivery investorCashAccount

  -- Settle batch
  [investorCashHoldingCid, bankCashHoldingCid, investorBondHoldingCid] <-
    submitMulti (S.toList settlers) [publicParty] do
      exerciseCmd result.batchCid Batch.Settle with actors = settlers

Following settlement, the investor receives a cash holding for the due coupon amount.

Atomic Settlement

In the non-atomic settlement case above, settlement was done in two steps: first from issuer to CSD and then from CSD to investor. In atomic settlement, this is done in on step.

The first part of the process is very similar. The first important difference is when the CSD exercises the ClaimEffect choice, where the bond holdings of both the CSD and the investor are provided:

  result <- submit csd do
    exerciseCmd lifecycleClaimRuleCid Claim.ClaimEffect with
      claimer = csd
      holdingCids = [csdBondHoldingCid, investorBondHoldingCid]
      effectCid
      batchId = Id "CouponSettlement"

There are now more settlement instructions (both from CSD to issuer and from issuer to CSD):

  let
    [   csdBondInstructionCid1      -- old bond from CSD to issuer
      , investorBondInstructionCid  -- old bond from investor to CSD
      , issuerBondInstructionCid    -- new bond from issuer to CSD
      , csdBondInstructionCid2      -- new bond from CSD to investor
      , issuerCashInstructionCid    -- coupon payment from issuer to CSD
      , csdCashInstructionCid       -- coupon payment from CSD to investor's bank
      , bankCashInstructionCid      -- coupon payment from investor's bank to investor
      ] = result.instructionCids

These will have to be allocated, approved and settled similarly to the non-atomic case above. See the file Instrument/Generic/Test/Intermediated/BondCoupon.daml for full details.

Frequently Asked Questions

What if one party wants to cancel the settlement?

The parties who sign the Batch contract (the requestors) can exercise the Cancel choice of the Batch to cancel all associated Instructions atomically.

Parties who are not a requestor can prevent settlement by not approving / allocating their instructions (since a batch is only successful if the settlement instructions are fully allocated and approved).