Intermediated Settlement¶
This tutorial expands upon the principles discussed in our previous Internal Settlement tutorial, providing an in-depth exploration of intermediated settlement involving a multi-level account hierarchy across different custodians.
Understanding Intermediated Settlement with Examples¶
This tutorial features two example scripts, runWrappedTransfersSettlement
and
runRouteProviderSettlement
, illustrating how to settle a batch with multiple instructions and
custodians.
Each script commences with runSetupIntermediatedSettlement
, initiating parties, establishing an
instrument issued by the Central Bank, and setting up an account hierarchy rooted at the Central
Bank. This hierarchy includes two custodian banks, Bank1 and Bank2, and their respective clients
Alice, Bob, and Charlie. Holdings are created for Alice@Bank1, Bank1@CentralBank, and Charlie@Bank2:
SetupIntermediatedSettlement
{ instrument
; bank1; bank2
; alice; aliceAccount; aliceHoldingCid
; bob; bobAccount2
; charlie; charlieAccount; charlieAccount2; charlieHoldingCid
; requestor
; settlementFactoryCid
} <- runSetupIntermediatedSettlement
The below diagram illustrates the setup, where edges represent accounts and stars (*
) denote
holdings:
Central Bank
*/ \
Bank1 Bank2
*/ \ */ \
Alice Charlie Bob
Wrapped Transfers: A Detailed Analysis¶
Our first example elucidates how two transfers at separate custodians can be consolidated into a single batch. The routed steps are as follows:
let
routedStep1 = RoutedStep with
custodian = bank1
sender = alice
receiver = charlie
quantity = qty 1000.0 instrument
routedStep2 = routedStep1 with
custodian = bank2
sender = charlie
receiver = bob
These steps are converted into a batch and instructions:
(batchCid, [instructionCid1, instructionCid2]) <-
submit requestor do
exerciseCmd settlementFactoryCid Settlement.Instruct with
instructors = fromList [requestor]
settlers = singleton requestor
id = Id "1"
description = "Transfer from Alice to Bob via Charlie"
contextId = None
routedSteps = [routedStep1, routedStep2]
settlementTime = None -- i.e., immediate settlement
The following diagram visualizes this pre- and post-settlement of the batch, where >
signifies
settlement instructions, and stars (*
) represents the holdings:
Central Bank Central Bank
*/ \ */ \
Bank1 Bank2 "Settle" Bank1 Bank2
*/ \ */ \ => / \* / \*
Alice > Charlie > Bob Alice Charlie Bob
T1 T2
Similar to the Internal Settlement tutorial, Alice allocates her Bank1 holding through a pledge, while Bob approves its instruction by taking delivery at his account at Bank2. The intermediary, Charlie, approves by taking delivery to his Bank1 account and allocates by pledging his pre-existing holding at Bank2:
-- i. Alice allocates.
(instructionCid1, _) <- submit alice do
exerciseCmd instructionCid1 Instruction.Allocate with
actors = S.singleton alice
allocation = Pledge $ toInterfaceContractId aliceHoldingCid
-- ii. Bob approves.
instructionCid2 <- submit bob do
exerciseCmd instructionCid2 Instruction.Approve with
actors = S.singleton bob
approval = TakeDelivery bobAccount2
-- iii. Charlie approves and allocates (with pass-through).
instructionCid1 <- submit charlie do
exerciseCmd instructionCid1 Instruction.Approve with
actors = S.singleton charlie
approval = TakeDelivery charlieAccount
instructionCid2 <- submit charlie do
exerciseCmd instructionCid2 Instruction.Allocate with
actors = S.singleton charlie
allocation = Pledge $ toInterfaceContractId charlieHoldingCid
-- iii. Requestor executes the settlement.
[charlieHoldingCid, bobHoldingCid] <- submit requestor do
exerciseCmd batchCid Batch.Settle with
actors = singleton requestor
Important to note, Charlie cannot pass-through Alice’s holding to Bob, as in the Internal Settlement tutorial, due to the holdings having different custodians. Therefore, for settlement to occur, Charlie needs a holding at Bank2. The settlement alters Charlie’s counterparty risk, shifting it from Bank1 to Bank2. This is a situation Charlie might wish to avoid. The upcoming example shows how to involve a route provider, eliminating the need for Charlie to hold any upfront holdings, thus preserving his counterparty exposure.
Route Provider: An Alternative Approach¶
Our second example follows a similar setup to the first, involving a settlement step between Alice and Charlie (S1), and another between Charlie and Bob (S2). However, these steps do not specify a custodian, unlike the routed steps.
To convert the settlement steps S1 and S2 into routed steps, we engage a route provider. The provider suggests preferable routes to the Central Bank for each client. During the “Discover” action, each step is transformed into a sequence of routed steps
- S1 -> (T1)
- S2 -> (D, T2, C)
where T1 and T2 denote settlements by transfers, D represents a Debit, and C signifies a Credit. The diagram below depicts the effects of the discovery and settlement process:
Central Bank Central Bank
"Discover" */ T2 \ "Settle" / *\
=> Bank1 > Bank2 => Bank1 > Bank2
/ D^ / C\ / \ / *\
Alice > Charlie > Bob */ \ / v Alice Charlie Bob
S1 S2 Alice > Charlie Bob
T1
Let’s begin with the “Discover” action:
let
quantity = qty 1000.0 instrument
-- Alice to Charlie step.
step1 = Step with sender = alice; receiver = charlie; quantity
-- Charlie to Bob step.
step2 = Step with sender = charlie; receiver = bob; quantity
-- Using a route provider to discover settlement route, with intermediaries, from Alice to Bob for
-- the instrument.
let
paths = M.fromList
[ ( show instrument.id
, Hierarchy with
rootCustodian = cb
pathsToRootCustodian = [[alice, bank1], [charlie, bank1], [bob, bank2]]
)
]
routeProviderCid <- toInterfaceContractId @RouteProvider.I <$> submit requestor do
createCmd IntermediatedStatic with provider = requestor; paths; observers = S.empty
routedSteps <- submit requestor do
exerciseCmd routeProviderCid RouteProvider.Discover with
discoverors = S.singleton requestor; contextId = None; steps = [step1, step2]
-- Sanity check.
let
routedStep1' = RoutedStep with custodian = bank1; sender = alice; receiver = charlie; quantity
routedStep2' = RoutedStep with custodian = bank1; sender = charlie; receiver = bank1; quantity
routedStep3' = RoutedStep with custodian = cb; sender = bank1; receiver = bank2; quantity
routedStep4' = RoutedStep with custodian = bank2; sender = bank2; receiver = bob; quantity
routedSteps === [routedStep1', routedStep2', routedStep3', routedStep4']
Followed by the creation of the batch and instructions:
(batchCid, [instructionCid1, instructionCid2, instructionCid3, instructionCid4]) <-
submit requestor do
exerciseCmd settlementFactoryCid Settlement.Instruct with
instructors = fromList [requestor]
settlers = singleton requestor
id = Id "1"
description = "Transfer from Alice to Bob via intermediaries"
contextId = None
routedSteps = routedSteps
settlementTime = None -- i.e., immediate settlement
Finally, Alice, Charlie, Bank1, Bank2, and Bob allocate and approve their instructions accordingly:
-- i. Alice allocates
(instructionCid1, _) <- submit alice do
exerciseCmd instructionCid1 Instruction.Allocate with
actors = S.singleton alice
allocation = Pledge $ toInterfaceContractId aliceHoldingCid
-- ii. Bob approves.
instructionCid4 <- submit bob do
exerciseCmd instructionCid4 Instruction.Approve with
actors = S.singleton bob
approval = TakeDelivery bobAccount2
-- iii. Charlie approves and allocates.
instruction2 <- retrieveKey charlie instructionCid2
instructionCid1 <- submit charlie do
exerciseCmd instructionCid1 Instruction.Approve with
actors = S.singleton charlie
approval = PassThroughTo (charlieAccount, instruction2)
instruction1 <- retrieveKey charlie instructionCid1
(instructionCid2, _) <- submit charlie do
exerciseCmd instructionCid2 Instruction.Allocate with
actors = S.singleton charlie
allocation = PassThroughFrom (charlieAccount, instruction1)
-- iv. Bank1 approves and allocates.
instructionCid2 <- submit bank1 do
exerciseCmd instructionCid2 Instruction.Approve with
actors = S.singleton bank1
approval = DebitSender
(instructionCid3, _) <- submit bank1 do
exerciseCmd instructionCid3 Instruction.Allocate with
actors = S.singleton bank1
allocation = Pledge $ toInterfaceContractId bank1HoldingCid
-- v. Bank2 approves and allocates.
instructionCid3 <- submit bank2 do
exerciseCmd instructionCid3 Instruction.Approve with
actors = S.singleton bank2
approval = TakeDelivery bank2Account
(instructionCid4, _) <- submit bank2 do
exerciseCmd instructionCid4 Instruction.Allocate with
actors = S.singleton bank2
allocation = CreditReceiver
-- vi. Requestor executes the settlement.
[charlierHoldingCid, bobHoldingCid] <- submit requestor do
exerciseCmd batchCid Batch.Settle with
actors = singleton requestor
Once the batch is settled, all instructions are executed atomically, causing a coordinated change in the account hierarchy’s holdings. Importantly, Charlie acted as an intermediary, providing a route from Alice to Bob, without having to use any holdings upfront.
Summary¶
You know how to define complex transactions and settle them atomically. Crucial points to remember are:
- A route provider serves the purpose of discovering settlement routes or routed steps for each settlement step.
- Viewing the account hierarchy as a tree — with the Central Bank at the root, custodians on the second level, and clients as leaves — transfers occur on horizontally directed routed steps, debits on upwards directed routed steps, and credits on downward directed routed steps.
As a challenge for the curious reader, try extending these examples to settle two instruments using settlement routes across two different account hierarchies.