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.