Multiple party agreement

The Multiple Party Agreement pattern uses a Pending contract as a wrapper for the Agreement contract. Any one of the signatory parties can kick off the workflow by creating a Pending contract on the ledger, filling in themselves in all the signatory fields. The Agreement contract is not created on the ledger until all parties have agreed to the Pending contract, and replaced the initiator’s signature with their own.

Motivation

The Initiate and Accept shows how to create bilateral agreements in DAML. However, a project or a workflow often requires more than two parties to reach a consensus and put their signatures on a multi-party contract. For example, in a large construction project, there are at least three major stakeholders: Owner, Architect and Builder. All three parties need to establish agreement on key responsibilities and project success criteria before starting the construction.

If such an agreement were modeled as three separate bilateral agreements, no party could be sure if there are conflicts between their two contracts and the third contract between their partners. If the Initiate and Accept were used to collect three signatures on a multi-party agreement, unnecessary restrictions would be put on the order of consensus and a number of additional contract templates would be needed as the intermediate steps. Both solution are suboptimal.

Following the Multiple Party Agreement pattern, it is easy to write an agreement contract with multiple signatories and have each party accept explicitly.

Implementation

Agreement contract

The Agreement contract represents the final agreement among a group of stakeholders. Its content can vary per business case, but in this pattern, it always has multiple signatories.

template ContractPlaceholder
  with
    signatories: [Party]
  where
    signatory signatories
    ensure
      unique signatories
  -- The rest of the template to be agreed to would follow here
Pending contract

The Pending contract needs to contain the contents of the proposed Agreement contract, as a parameter. This is so that parties know what they are agreeing to, and also so that when all parties have signed, the Agreement contract can be created.

The Pending contract has a list of parties who have signed it, and a list of parties who have yet to sign it. If you add these lists together, it has to be the same set of parties as the signatories of the Agreement contract.

All of the toSign parties have the choice to Sign. This choice checks that the party is indeed a member of toSign, then creates a new instance of the Pending conract where they have been moved to the signed list.

template Pending
  with
    finalContract: ContractPlaceholder
    alreadySigned: [Party]
  where
    signatory alreadySigned
    observer finalContract.signatories
    ensure
      -- Can't have duplicate signatories
      unique alreadySigned

    -- The parties who need to sign is the finalContract.signatories with alreadySigned filtered out
    let toSign = filter (`notElem` alreadySigned) finalContract.signatories

    choice Sign : ContractId Pending with
        signer : Party
      controller signer
        do
          -- Check the controller is in the toSign list, and if they are, sign the Pending contract
          assert (signer `elem` toSign)
          create this with alreadySigned = signer :: alreadySigned

Once all of the parties have signed, any of them can create the final Agreement contract using the Finalize choice. This checks that all of the signatories for the Agreement have signed the Pending contract.

    choice Finalize : ContractId ContractPlaceholder with
        signer : Party
      controller signer
        do
          -- Check that all the required signatories have signed Pending
          assert (sort alreadySigned == sort finalContract.signatories)
          create finalContract
Collecting the signatures in practice

Since the final Pending contract has multiple signatories, it cannot be created in that state by any one stakeholder.

However, a party can create a pending contract, with all of the other parties in the toSign list.

  parties@[person1, person2, person3, person4] <- makePartiesFrom ["Alice", "Bob", "Clare", "Dave"]
  let finalContract = ContractPlaceholder with signatories = parties

  -- Parties cannot create a contract already signed by someone else
  initialFailTest <- person1 `submitMustFail` do
    create Pending with finalContract; alreadySigned = [person1, person2]

  -- Any party can create a Pending contract provided they list themselves as the only signatory
  pending <- person1 `submit` do
    create Pending with finalContract; alreadySigned = [person1]

Once the Pending contract is created, the other parties can sign it. For simplicity, the example code only has choices to express consensus (but you might want to add choices to Accept, Reject, or Negotiate).

  -- Each signatory of the finalContract can Sign the Pending contract
  pending <- person2 `submit` do
    exercise pending Sign with signer = person2
  pending <- person3 `submit` do
    exercise pending Sign with signer = person3
  pending <- person4 `submit` do
    exercise pending Sign with signer = person4

  -- A party can't sign the Pending contract twice
  pendingFailTest <- person3 `submitMustFail` do
    exercise pending Sign with signer = person3
  -- A party can't sign on behalf of someone else
  pendingFailTest <- person3 `submitMustFail` do
    exercise pending Sign with signer = person4

Once all of the parties have signed the Pending contract, any of them can then exercise the Finalize choice. This creates the Agreement contract on the ledger.

  person1 `submit` do
    exercise pending Finalize with signer = person1
../../_images/multiplepartyAgreement.png

Multiple Party Agreement Diagram