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:
- Creating a Generic Instrument modeling a fixed rate bond
- Defining an intermediated settlement route
- Defining a suitable lifecycle event
- Lifecycling the instrument
- Non-atomic settlement of the lifecycle effects
- 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).