Intermediated Lifecycling of a Generic Instrument¶
This tutorial describes the lifecycle 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 fixed-rate bond instrument
- Defining an intermediated settlement route
- Defining a suitable lifecycle event
- Lifecycling the bond instrument
- Non-atomic settlement
- Atomic settlement
To follow the script used in this tutorial, you can
clone the Daml Finance repository. In particular,
the file src/test/daml/Daml/Finance/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 a Fixed-Rate Bond Instrument¶
We start by defining a fixed rate bond, which pays a 4% p.a. coupon with a 6M coupon period. This is explained in the Generic Tutorial.
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 (singleton issuer) today empty
Lifecycle the Bond Instrument¶
Using the Lifecycle interface, the CSD creates a lifecycle rule contract:
-- Create a lifecycle rule
lifecycleRuleCid : ContractId Lifecycle.I <- toInterfaceContractId <$> submit csd do
createCmd Lifecycle.Rule with
providers = 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 = singleton csd; observersToAdd = ("Issuer", 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 = fromList [investor]; paths = routes
settlementFactoryCid <- submit csd do
toInterfaceContractId <$> createCmd Factory with provider = csd; observers = fromList [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 = singleton csd
claimers = 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 = singleton investor; allocation = Pledge investorBondHoldingCid
(csdBondInstructionCid, _) <- submit csd do
exerciseCmd csdBondInstructionCid Instruction.Allocate with
actors = singleton csd; allocation = CreditReceiver
(csdCashInstructionCid, _) <- submit csd do
exerciseCmd csdCashInstructionCid Instruction.Allocate with
actors = singleton csd; allocation = Pledge cashHolding
(bankCashInstructionCid, _) <- submit bank do
exerciseCmd bankCashInstructionCid Instruction.Allocate with
actors = singleton bank; allocation = CreditReceiver
-- Approve instructions
investorBondInstructionCid <- submit csd do
exerciseCmd investorBondInstructionCid Instruction.Approve with
actors = singleton csd; approval = DebitSender
csdBondInstructionCid <- submit investor do
exerciseCmd csdBondInstructionCid Instruction.Approve with
actors = singleton investor; approval = TakeDelivery investorSecuritiesAccount
csdCashInstructionCid <- submit bank do
exerciseCmd csdCashInstructionCid Instruction.Approve with
actors = singleton bank; approval = TakeDelivery bankCashAccount
bankCashInstructionCid <- submit investor do
exerciseCmd bankCashInstructionCid Instruction.Approve with
actors = singleton investor; approval = TakeDelivery investorCashAccount
-- Settle batch
[investorBondHoldingCid, bankCashHoldingCid, investorCashHoldingCid] <-
submitMulti (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
, issuerBondInstructionCid -- new bond from issuer to CSD
, issuerCashInstructionCid -- coupon payment from issuer to CSD
, investorBondInstructionCid -- old bond from investor to CSD
, csdBondInstructionCid2 -- new bond from CSD to investor
, 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 src/test/daml/Daml/Finance/Instrument/Generic/Test/Intermediated/BondCoupon.daml
for full details.