How can I implement the case that the contract requires dynamic numbers of signatories?

Here’s my code and I don’t kown how to continue.

template ContractWithMultiSignatories
  with
    signatories: [Party]
  where
    signatory signatories

template ContracProposal
  with
    contract: ContractWithMultiSignatories
    submitter: Party
    signed: [Party]
  where
    signatory submitter
    observer contract.signatories
    controller contract.signatories can
      Accept : ContractId ContractWithMultiSignatories
        do
          assertMsg "Somebody hasn't signed" (contract.signatories == signed)
          create contract
          

contract_with_multi_signatories_test = do
  user1 <- allocateParty "user1"
  user2 <- allocateParty "user2"
  user3 <- allocateParty "user3"

  proposalId1 <- submit user1 do
    createCmd ContracProposal with
      submitter = user1
      signed = [user1]
      contract = ContractWithMultiSignatories with
        signatories = [user1, user2, user3]

  Some proposal1 <- queryContractId user2 proposalId1
  proposalId2 <- submit user2 do
    createCmd proposal1 with
      signed = user2 :: proposal1.signed
  

  Some proposal2 <- queryContractId user3 proposalId2
  proposalId3 <- submit user3 do
    createCmd proposal2 with
      signed = user3 :: proposal2.signed

  contractId <- submit user3 do
    exerciseCmd proposalId3 Accept

  pure()
1 Like

Here is an example below, where a selected signatory (I call it host) invites a new one. It does not allow concurrent invitations, but I wanted to keep it very simple.

You’ll probably find useful the following blogposts. The first show a slightly different approach, the second gives you some context on the flexible controller I used.

module Multi where

import Daml.Script

template ContractWithMultiSignatories
  with
    signatories: [Party]
  where
    signatory signatories
    choice Invite : ContractId Invitation with
       host : Party
       invitee : Party
     controller host
        do
          -- One could add constraints like: assert (host `elem` signatories)
          create Invitation with
            signatories
            host
            invitee

template Invitation
  with
    signatories: [Party]
    host : Party
    invitee : Party
  where
    signatory signatories
    controller invitee can
      Accept : ContractId ContractWithMultiSignatories
        do
          create ContractWithMultiSignatories with signatories = invitee::signatories

invite_two_more_signatories = do
  user1 <- allocateParty "user1"
  user2 <- allocateParty "user2"
  user3 <- allocateParty "user3"
  first <- submit user1 do
    createCmd ContractWithMultiSignatories with
      signatories = [user1]
  invitationForUser2 <- submit user1 do
    exerciseCmd first Invite with
      host = user1
      invitee = user2
  second <- submit user2 do
    exerciseCmd invitationForUser2 Accept
  invitationForUser3 <- submit user1 do
    exerciseCmd second Invite with
      host = user1
      invitee = user3
  submit user3 do
    exerciseCmd invitationForUser3 Accept
2 Likes

The syntax controller contract.signatories can means “all signatories together can do this thing”, ie you need all of their authority. What you want is for any of them to be able to sign. To do that, you need to use the advanced choice syntax @Richard_Kapolnai used in his example as well:

template ContractWithMultiSignatories
  with
    signatories: [Party]
  where
    signatory signatories

template ContracProposal
  with
    contract: ContractWithMultiSignatories
    signed: [Party]
  where
    signatory signed
    observer contract.signatories

    choice Accept : ContractId ContractWithMultiSignatories
      with
        accepter : Party
      controller accepter
        do
          assertMsg "accepter needs to be a signatory" (accepter `elem` contract.signatories)
          assertMsg "Somebody hasn't signed" (dedupSort contract.signatories == dedupSort signed)
          create contract

    choice Sign : ContractId ContracProposal
      with
        signer : Party
      controller signer
        do
          assertMsg "signer needs to be a signatory" (signer `elem` contract.signatories)
          create this with
            signed = dedupSort (signer :: signed)
          

contract_with_multi_signatories_test = do
  user1 <- allocateParty "user1"
  user2 <- allocateParty "user2"
  user3 <- allocateParty "user3"

  proposalId <- submit user1 do
    createCmd ContracProposal with
      signed = [user1]
      contract = ContractWithMultiSignatories with
        signatories = [user1, user2, user3]

  proposalId <- submit user2 do
    exerciseCmd proposalId Sign with
      signer = user2

  proposalId <- submit user3 do
    exerciseCmd proposalId Sign with
      signer = user3
  
  contractId <- submit user3 do
    exerciseCmd proposalId Accept with
      accepter = user3

  pure()
2 Likes