2. Business Logic and Types

An Escrow instance is fully specified by the following information:

  • Receiver’s address.

  • Sender’s address.

  • Asset class and amount of tokens that the sender paid.

  • Asset class and amount of tokens that the receiver should pay.

As we mentioned before, in the design we chose for this implementation, the sender starts an escrow by submitting a transaction that pays to a script an amount of some asset class. The script is parameterized on the receiver address, and the datum contains the rest of the information: sender’s address, asset class and amount of tokens that the receiver must pay. For validating the resolution of an escrow we need to check that when the script UTxO is being spent, the sender receives the corresponding amount of tokens, and the transaction is signed by the receiver. For validating that an escrow is canceled, the transaction spending the script UTxO must be signed by the sender.

In the Business module we implement the data type corresponding to the state of the dApp that will be located inside the Datum. We also implement the core checks and computations that will be used when building and validating the transactions.

In the Types module we implement the data types corresponding to the Datum and Redeemer, and some other boilerplate.

2.1. Business

We define some types for making eaiser to reason about the roles in an escrow. Both sender and receiver are identified by a WalletAddress, but we define wrappers over it:

newtype SenderAddress = SenderAddress { sAddr :: WalletAddress }
    deriving newtype (HP.Eq, HP.Show, Eq, FromJSON, ToJSON)

newtype ReceiverAddress = ReceiverAddress { rAddr :: WalletAddress }
    deriving newtype (HP.Show, Eq, FromJSON, ToJSON)

Thus, the validator parameter will be ReceiverAddress, and the remaining information needed once a escrow is started is defined in the following data-type:

data EscrowInfo = EscrowInfo
                  { sender      :: SenderAddress
                  , rAmount     :: Integer
                  , rAssetClass :: AssetClass
                  }
    deriving (HP.Eq, HP.Show, Generic)
    deriving anyclass (FromJSON, ToJSON)

We have now all the necessary ingredients to define the core functions needed for building and validating the resolution or cancellation of an escrow.

{-# INLINABLE signerIsSender #-}
signerIsSender :: PubKeyHash -> SenderAddress -> Bool
signerIsSender pkh SenderAddress{..} =
    pubKeyHashInAddress pkh (fromWalletAddress sAddr)

{-# INLINABLE signerIsReceiver #-}
signerIsReceiver :: PubKeyHash -> ReceiverAddress -> Bool
signerIsReceiver pkh ReceiverAddress{..} =
    pubKeyHashInAddress pkh (fromWalletAddress rAddr)

{-# INLINABLE valueToSender #-}
valueToSender :: EscrowInfo -> Value
valueToSender EscrowInfo{..} = assetClassValue rAssetClass rAmount

To resolve an escrow, the transaction signer must be the Receiver, and signerIsReceiver should be used when validating. In addition to that, the transaction must pay to the sender the corresponding value specified in the EscrowInfo. The function valueToSender should be used for computing that value at the moment of building the transaction (off-chain), and for validating it (on-chain). Similarly, for validating the cancellation of an escrow, function signerIsSender should be used.

Due to the simplicity of this dApp example, the Business logic is a short module and doesn’t contain too much code to be shared between off-chain and on-chain. In other cases with a complex state, much more logic should be implemented in the Business module. Nevertheless, even simple, this example still has code that is critical and can be used both for building and validating the transactions.

2.2. Types

In the Types module we basically define the validator Parameter, Datum and Redeemer types.

The Datum contains the Escrow info together with the asset class of the control token that is minted at start. It’s needed for knowing which token must be burned at resolving or canceling, and cannot be located in the parameter due to a circularity problem (we’ll explain more about this later).

data EscrowDatum = EscrowDatum
                   { eInfo       :: EscrowInfo
                   , eAssetClass :: AssetClass
                   }
    deriving Show

The Redeemer type specifies the different ways to spend a script UTxO. In this case we have two: resolve or cancel.

data EscrowRedeemer = CancelEscrow
                    | ResolveEscrow

The rest of the code in the module is mostly boilerplate.