This tutorial introduces the lifecycling framework of the library with a simple example. The purpose is to demonstrate how lifecycle rules and events can be used to process a dividend payment.
We are going to:
- create a new version of the token instrument
- create the required lifecycle rules
- create a distribution event
- process the event to produce the effects from the distribution
- instruct settlement by presenting a token holding
- settling the resulting batch atomically
This example builds on the previous Settlement tutorial script in the sense that the same accounts and the existing holdings are used.
Overview of the Process¶
We first give a high-level outline of the lifecycle process:
||A lifecycle rule implements the logic to calculate effects for a given lifecycle event. In our example we create a distribution rule to handle the dividend event on our token.|
||The lifecycle event refers to the target instrument the event applies to. Holdings on this instrument can then be used to claim the resulting lifecycle effect.|
||The lifecycle rule contains the business logic to derive the lifecycle effects resulting from a concrete event. The effect describes the per-unit holding transfers that are to be settled between a custodian and the owner of a holding.|
||The claim rule is used to claim the effects resulting from a lifecycle event using a holding on the target instrument. The result is a set of settlement instructions and a corresponding batch to be settled between the custodian and the owner of the holding.|
Run the Script¶
The code for this tutorial can be executed via the
runLifecycling function in the
The first part executes the script from the previous Settlement tutorial to arrive at the initial state for this scenario.
Then we create a new version of the token instrument, which is required for defining the distribution event. This is what the instrument holders will receive when processing the lifecycle event later in the tutorial.
let newTokenInstrument = tokenInstrument with version = "1" now <- getTime submit bank do exerciseCmd tokenFactoryCid Token.Create with token = Token with instrument = newTokenInstrument description = "Instrument representing units of a generic token after the distribution event" validAsOf = now observers = M.empty
Next, we create two lifecycle rules:
distributionRuleCid <- toInterfaceContractId @Lifecycle.I <$> submit bank do createCmd Distribution.Rule with providers = S.singleton bank lifecycler = bank observers = S.singleton bob id = Id "Lifecycle rule for distribution" description = "Rule contract to lifecycle an instrument following a distribution event" lifecycleClaimRuleCid <- toInterfaceContractId @Claim.I <$> submitMulti [bank, bob]  do createCmd Claim.Rule with providers = S.fromList [bank, bob] claimers = S.singleton bob settlers = S.singleton bob routeProviderCid settlementFactoryCid netInstructions = False
- The Distribution Rule defines the business logic to calculate the resulting lifecycle effect from a given distribution event. It is signed by the Bank as a provider.
- The Claim Rule allows a holder of the target instrument to claim the effect resulting from the distribution event. By presenting their holding they can instruct the settlement of the holding transfers described in the effect.
We then create a distribution event describing the terms of the dividend to be paid.
distributionEventCid <- toInterfaceContractId @Event.I <$> submit bank do createCmd Distribution.Event with providers = S.singleton bank id = Id "DISTRIBUTION" description = "Profit distribution" effectiveTime = now targetInstrument = tokenInstrument newInstrument = newTokenInstrument perUnitDistribution = [qty 0.02 usdInstrument] observers = S.empty
Now we can process the distribution event using the distribution rule.
(_, [effectCid]) <- submit bank do exerciseCmd distributionRuleCid Lifecycle.Evolve with eventCid = distributionEventCid observableCids =  instrument = tokenInstrument
The result of this is an effect describing the per-unit asset movements to be executed for token holders. Each holder can now present their holding to claim the effect and instruct settlement of the associated entitlements.
result <- submit bob do exerciseCmd lifecycleClaimRuleCid Claim.ClaimEffect with claimer = bob holdingCids = [bobHoldingCid] effectCid -- This is equivalent to writing effectCid = effectCid batchId = Id "DistributionSettlement" let [bobInstructionCid, bankInstructionCid, couponInstructionCid] = result.instructionCids
As a side-effect of settling the entitlements, the presented holding is exchanged for a holding of the new token version. This is to prevent a holder from benefiting from a given effect twice.
In our example of a cash dividend, only a single instruction is generated: the movement of cash from the bank to the token holder. This instruction along with its batch is settled the usual way, as described in the previous Settlement tutorial.
-- Allocate instruction (bobInstructionCid, _) <- submit bob do exerciseCmd bobInstructionCid Instruction.Allocate with actors = S.singleton bob allocation = Pledge bobHoldingCid (bankInstructionCid, _) <- submit bank do exerciseCmd bankInstructionCid Instruction.Allocate with actors = S.singleton bank allocation = CreditReceiver (couponInstructionCid, _) <- submit bank do exerciseCmd couponInstructionCid Instruction.Allocate with actors = S.singleton bank allocation = CreditReceiver -- Approve instruction bobInstructionCid <- submit bank do exerciseCmd bobInstructionCid Instruction.Approve with actors = S.singleton bank approval = DebitSender bankInstructionCid <- submit bob do exerciseCmd bankInstructionCid Instruction.Approve with actors = S.singleton bob approval = TakeDelivery bobAccount couponInstructionCid <- submit bob do exerciseCmd couponInstructionCid Instruction.Approve with actors = S.singleton bob approval = TakeDelivery bobAccount -- Settle batch submit bob do exerciseCmd result.batchCid Batch.Settle with actors = S.singleton bob
Note that the bank in this case does not actually transfer the cash from another account, but simply
credits Bob’s account by using the
CreditReceiver allocation type. In a real-world dividend
scenario one would additionally model the flow of funds from the issuer to the bank using the same
lifecycle process as described above.
Frequently Asked Questions¶
Which party should create and sign the lifecycle rules and events?¶
In the simplified scenario for this tutorial, we have used the bank as both the issuer and depository for the instruments involved. In a real-world case, instruments and their corresponding lifecycle rules and events would be maintained by an actual issuer, with the depository acting as a 3rd-party trust anchor.
Which parties typically take which actions in the lifecycle workflow?¶
The lifecycle interfaces governing the process leave the controllers of the various choices in the process up to the implementation.
- Typically, we would expect the issuer of an instrument to be responsible to generate lifecycle events (for example, announcing dividends or stock splits).
- Lifecycle rules on the other hand are often controlled by 3rd-party calculation agents.
- The claiming of lifecycle effects is by default the responsibility of the owner of a holding. If instead the owner wants to delegate this responsibility to their custodian they can do so via a delegation contract.
- The party executing settlement can be chosen as well, as described in the previous tutorial on Settlement.
Which party should take the role as lifecycler?¶
From a design perspective, a lifecycler is often the party that defines the lifecycle events happening on an instrument (although they can be different). In the simplified example above, it is the bank. In a more realistic example, it would probably be the issuer. In some special cases, if we really need the owner to be the lifecycler, we can use a delegation contract.
The lifecycler is currently trusted with:
- Timely and complete Event processing
- Providing accurate Observations
Which party is the provider of the Effect?¶
Most of the time the provider of the Effect is the lifecycler. However, in some cases we may want to avoid disclosing the claimed holdings to the lifecycler. The provider of the Effect gets to see all holdings claimed against that one Effect contract. If we wish to avoid that, we then need a different effect provider.
Can an instrument act as its own lifecycle rule?¶
Yes, an instrument can implement the
Lifecycle interface directly such that the lifecycle rules
are contained within the instrument itself. There are, however, advantages to separating this logic
out into rule contracts:
- Keeping lifecycle rules in a different package from your instruments allows you to independently upgrade or patch them without affecting your live instruments.
- Having separate rules allows to change the lifecycle properties of an instrument dynamically at runtime. For example, an instrument can initially be created without support for doing asset distributions. Then, at a later point, the issuer might decide to start paying dividends. They can now simply add a distribution rule to the running system to enable this new lifecycle event for their instrument without affecting the actual live instrument itself (or any holdings on it).
Can I integrate a holding ownership change (of the target instrument) within lifecycling?¶
Lifecycling will not change the ownership of the target instrument. You should use the Transfer pattern to do a delivery-versus-payment as a separate step from the lifecycling.
However, there usually is a change of ownership of the other consumed/produced instruments when lifecycling (e.g. when paying out a dividend cash is moved from one party to another).
You have learned how to use lifecycle rules and events to describe the behavior of an instrument. The key concepts to take away are:
- Lifecycle events represent different ways of how an instrument can evolve.
- A lifecycle rule contains logic to calculate the effects an event has on an instrument and its holdings.
- A claim rule is used to instruct settlement for a given effect using a holding.