Locking by safekeeping

Safekeeping is a realistic way to model locking as it is a common practice in many industries. For example, during a real estate transaction, purchase funds are transferred to the sellers lawyer’s escrow account after the contract is signed and before closing. To understand its implementation, review the original Coin template first.

template Coin
  with
    owner: Party
    issuer: Party
    amount: Decimal
    delegates : [Party]
  where
    signatory issuer, owner
    observer delegates

    controller owner can

      Transfer : ContractId TransferProposal
        with newOwner: Party
        do   
            create TransferProposal
             with coin=this; newOwner

    --a coin can only be archived by the issuer under the condition that the issuer is the owner of the coin. This ensures the issuer cannot archive coins at will.
    controller issuer can
      Archives
        : ()
        do assert (issuer == owner)

There is no need to make a change to the original contract. With two additional contracts, we can transfer the Coin ownership to a locker party.

  • Introduce a separate contract template LockRequest with the following features:
    • LockRequest has a locker party as the single signatory, allowing the locker party to unilaterally initiate the process and specify locking terms.
    • Once owner exercises Accept on the lock request, the ownership of coin is transferred to the locker.
    • The Accept choice also creates a LockedCoinV2 that represents Coin in locked state.
template LockRequest
  with
    locker: Party
    maturity: Time
    coin: Coin
  where
    signatory locker

    controller coin.owner can
      Accept : LockResult
        with coinCid : ContractId Coin
        do   
          inputCoin <- fetch coinCid
          assert (inputCoin == coin)
          tpCid <- exercise coinCid Transfer with newOwner = locker
          coinCid <- exercise tpCid AcceptTransfer
          lockCid <- create LockedCoinV2 with locker; maturity; coin
          return LockResult {coinCid; lockCid}
  • LockedCoinV2 represents Coin in the locked state. It is fairly similar to the LockedCoin described in Consuming choice. The additional logic is to transfer ownership from the locker back to the owner when Unlock or Clawback is called.
template LockedCoinV2
  with
    coin: Coin
    maturity: Time
    locker: Party
  where
    signatory locker, coin.owner

    controller locker can
      UnlockV2
        : ContractId Coin
        with coinCid : ContractId Coin
        do   
          inputCoin <- fetch coinCid
          assert (inputCoin.owner == locker)
          tpCid <- exercise coinCid Transfer with newOwner = coin.owner
          exercise tpCid AcceptTransfer

    controller coin.owner can
      ClawbackV2
        : ContractId Coin
        with coinCid : ContractId Coin
        do   
          currTime <- getTime
          assert (currTime >= maturity)
          inputCoin <- fetch coinCid
          assert (inputCoin == coin with owner=locker)
          tpCid <- exercise coinCid Transfer with newOwner = coin.owner
          exercise tpCid AcceptTransfer
../../../_images/lockingBySafekeeping.png

Locking By Safekeeping Diagram

Trade-offs

Ownership transfer may give the locking party too much access on the locked asset. A rogue lawyer could run away with the funds. In a similar fashion, a malicious locker party could introduce code to transfer assets away while they are under their ownership.