How do you manage authorization and visibility in trades between two parties without an intermediary?

Hi all,

Looking for a little advice around the selling of an object directly between buyer and seller with no intermediary.

The seller owns a Token contract (with an amount field), a Cash contract, and has created a SellOffer contract, stipulating he is willing to sell X tokens for Y cash.

The SellOffer has a [Party] field for the observers, but none are able to see the seller’s Token or Cash, and so I’m running in to an issue with regards to a buyer wishing to complete the trade.

The primary aim is to complete the transaction with only one user action. I could, for example, create a LockedToken contract with the SellOffer, and then the buyer archives that to receive tokens and creates a TransferredCash contract for the seller to receive corresponding cash. But that would require a second action by the seller to retrieve the cash and update their Cash contract.

Any insight would be greatly appreciated!

It’s hard to give a specific advice without knowing the details of your model. Particularly who are the signatories on the Token and the Cash templates.
The workflow you’re looking to implement has a name. It’s called atomic swap. As the name implies, in this workflow two assets (in your case the Token and the Cash) are exchanged between two parties in a single transaction. You may take a look at the example of atomic swap implementation in the Wallet Daml Sample App. The repository contains a writeup on the atomic swap workflow implementation in this app.
The asset model in the Wallet Daml Sample App utilizes an issuer party, whose role is to ensure the integrity of the system and its freedom from uncontrolled issuance or double spend. If in your model you don’t utilize an issuer party, the atomic swap implementation in the Wallet Daml Sample App may be overly complicated for your model.
From your description I assume that the owners of Token and Cash contracts are signatories on the Token and the Cash templates respectively. You need both seller’s and buyer’s authority to change the owner on the Token and the Cash contracts. You get the seller’s authority from seller being a signatory on the SellOffer contract. And you get the buyer’s authority from buyer being the controller on the Accept_SellOffer choice. You don’t need a TransferredCash or any other additional templates to satisfy the authorization and privacy rules.
The purpose of the LockedToken template, which you mentioned, would be to provide the buyer visibility of the token. If the buyer does not have visibility of the token, then the fetch of the token in the Accept_SellOffer choice will fail. This said, you don’t necessarily need a separate template for that. You could just add the buyer as an observer to the Token contract when the seller’s making the sell offer.
Here’s a complete example with a test script covering the happy path. Note that in this model there’s no protection against uncontrolled issuance. Any party can issue any amount of Token and Cash at any time.

template Token
  with
    owner : Party
    amount : Decimal
    observers : [Party]

  where
    signatory owner
    observer observers

template Cash
  with
    owner : Party
    amount : Decimal

  where
    signatory owner

template SellOffer
  with
    seller : Party
    tokenCid : ContractId Token
    buyer : Party
    price : Decimal

  where
    signatory seller
    observer buyer

    choice Cancel_SellOffer : ContractId Token
      controller seller
      do
        token <- fetch tokenCid
        archive tokenCid
        create token with
          observers = []

    choice Reject_SellOffer : ContractId Token
      controller buyer
      do
        token <- fetch tokenCid
        archive tokenCid
        create token with
          observers = []

    choice Accept_SellOffer : ()
      with
        cashCid : ContractId Cash
      controller buyer
      do
        token <- fetch tokenCid
        archive tokenCid
        create token with 
          owner = buyer
          observers = []
        cash <- fetch cashCid
        assertMsg "The amount of cash provided does not match the price of the token" $
          cash.amount == price
        archive cashCid
        create cash with owner = seller
        return ()

testAtomicSwap = script do
  buyerParty <- allocateParty "Buyer"
  sellerParty <- allocateParty "Seller"
  tokenCid <- submit sellerParty do
    createCmd Token with
      owner = sellerParty
      amount = 10.0
      observers = [buyerParty]
  cashCid <- submit buyerParty do
    createCmd Cash with
      owner = buyerParty
      amount = 100.0
  sellOfferCid <- submit sellerParty do
    createCmd SellOffer with
      seller = sellerParty
      tokenCid
      buyer = buyerParty
      price = 100.0
  submit buyerParty do
    exerciseCmd sellOfferCid Accept_SellOffer with
      cashCid

  return ()

Thanks for the reply!

You made some good points, and I definitely needed to look through the wallet example, but there’s two problems I’m seeing: visibility and merging.

Both in your little example and in the wallet repo, the traded assets are visible to both parties after the trade has completed. This will not be acceptable, and it’s a reasonable argument, if I purchase something from you with cash I should not be able to see what you do with that cash afterwards.

We also don’t want to have multiple instances of Cash, at least not when the currency is the same, and merging existing instances of Cash is not possible for the buyer because once again, they would need visibility of all the seller’s cash.

@lashenhurst
You’re correct in your observation that the transferred token is visible to the seller, even though the seller is not a stakeholder on the contract. Similarly the cash contract is visible to the buyer, even though the buyer is not a stakeholder on the transferred cash contract. This effect, where contracts may be visible to non-stakeholders, is known as Divulgence.
Divulgence is one reason we model assets as discrete contracts in Daml apps. The Wallet Daml Sample App repo contains a writeup on this topic. Another reason for implementing assets as a set of discrete contracts as opposed to a single contract representing the account balance, is that the discrete model (known as UTXO) reduces the contention on asset contracts and improves the performance of the system.
To summarize, contrary to your requirement, we strongly recommend modeling your system with multiple instances of Cash contracts. Our Daml Finance library, which supports the modeling of financial and non-financial use cases in Daml and provides the implementation of generic delivery-vs-payment and immediate, guaranteed settlement workflows, models assets as discrete contracts. Perhaps you’d like to look into utilizing Daml Finance library, which may accelerate the delivery of your project?

1 Like