5. Plutus Application Backend

Now we reviewed the core pieces of the server implementation, it’s turn to take a look to the web service. It’s implemented using the PAB module from plutus-apps libraries.

As we mentioned before, in our approach we use some features of the plutus-apps libraries, and discard others. The PAB library allows to implement a web service providing the API for interacting with the off-chain code of the dApp. We just need a subset of that API containing the operations activate, endpoint, status and stop.

For interacting with the PAB service it’s necessary to get an instance id by calling activate. The activation provides an interface through dApp endpoints, defined in a schema. Different schemas will offer different interfaces, which are chosen at activation. In our case we have only one, but other complex dApps could provide more. The activation could require some parameters which are instantiated and will persist while the instance is alive. In the Escrow example, the client connects with the PAB by activating an instance providing a wallet address. After that, the four endpoints defined in the schema are accessible for performing the operations. For any communication from the server to the client, as sending unbalanced transactions or other blockchain query responses, the status endpoint is used. Finally, an instance can be deleted by calling stop.

5.1. Endpoints Specification

Let’s review how this flow we described above works for the Escrow example. One of the key points of design is the schema or endpoints’ set. We saw how to define this on the Off-chain section, now is time to see how this is related to the PAB service. Let’s consider an example where the sender wants to exchange 10 tokens A by 20 tokens B.

We have only one kind of activation, and will let us interact with the endpoints defined on the EscrowSchema. It needs the user’s WalletAddress.

Activating an instance is performed by the api/contract/activate PAB endpoint, requiring the following information in the body

{
   "caID":{
      "waPayment":{
         "getPubKeyHash":"PUBKEYHASH"
      },
      "waStaking":{
         "getPubKeyHash":"PUBKEYHASH"
      }
   },
   "caWallet":{
      "getWalletId":0000000000000000000000000000000000000000
   }
}

This JSON has two main fields: caID and caWallet. The latter is necessary if the PAB is used with cardano-wallet, so we set any number (given that we don’t use that functionality). The important information goes inside caID, where basically we specify the kind of activation we are doing and the corresponding parameters. In our case we have only one kind of activation, whose parameter is a wallet address, specified by payment pub key hash (waPayment) and staking pub key hash (waStaking).

The response of the activation call is an instance id, that is necessary for calling the off-chain operations. The PAB endpoint for calling them depends on the schema exposed in the activation: api/contract/instance/[instance-id]/endpoint/[off-chain operation].

For instance, if we want to perform an start for creating a escrow, we must call api/contract/instance/[instance-id]/endpoint/start specifying in the body the StartParams information on JSON format.

{
   "receiverAddress":{
      "waPayment":{
         "getPubKeyHash":"PUBKEYHASH"
      },
      "waStaking":{
         "getPubKeyHash":"PUBKEYHASH"
      }
   },
   "sendAssetClass":{
      "unAssetClass":[
         {
            "unCurrencySymbol":"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
         },
         {
            "unTokenName":"A"
         }
      ]
   },
   "sendAmount":10,
   "receiveAssetClass":{
      "unAssetClass":[
         {
            "unCurrencySymbol":"bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"
         },
         {
            "unTokenName":"B"
         }
      ]
   },
   "receiveAmount":20
}

Notice that we don’t need to pass again the wallet address of the user performing the operation, because it is set at activation and it persists until the instance is stopped. For doing that, the PAB endpoint stop is called: api/contract/instance/[instance-id]/stop.

5.2. Implementation

Let’s briefly review how the web service is implemented. It’s mainly boilerplate, we just need to connect the PAB activation with the corresponding off-chain code. Inside app folder we find two modules: Handlers, containing the code for connecting the PAB activation with the corresponding off-chain code, and Main which contains the main function of the web service.

Inside Handlers we define the data type Escrow that will represent the different ways of connection, this is one of the key parts of the module. Each different connection will have its own set of endpoints.

newtype Escrow = Connect WalletAddress
    deriving (Eq, Ord, Show, Generic)
    deriving anyclass (FromJSON, ToJSON, ToSchema)

As we mentioned before, in our example we have only one kind of activation, so our Escrow type has a unique constructor that we call Connect. It receives the activation parameter, which in this case is a WalletAddress.

Now the only remaining part is to relate the Escrow type with the off-chain code. Remember that we defined an endpoints function in OffChain.Operations module:

endpoints
    :: WalletAddress
    -> Contract (Last [UtxoEscrowInfo]) EscrowSchema Text ()
endpoints raddr = forever $ handleError logError $ awaitPromise $
                  startEp `select` cancelEp `select` resolveEp `select` reloadEp
  where
    .....
    .....

This function implements an infinite loop exposing the dApp endpoints corresponding to each operation. Connecting this function with the PAB handler is mainly boilerplate:

instance HasDefinitions Escrow where
    getDefinitions = []
    getSchema      = const []
    getContract    = getEscrowContract

getEscrowContract :: Escrow -> SomeBuiltin
getEscrowContract (Connect wa) = SomeBuiltin $ endpoints wa

We need to instantiate the HasDefinitions typeclass, where the only function that we are interested on is getContract. The others are not necessary for implementing the web service in our approach, so we complete them with a trivial definition. In getContract we basically specify which off-chain function is called for each constructor of the Escrow type, i.e., for each way of activation. We have only one and corresponds to the endpoints function.

Finally, inside Main module we have the main function that runs the service. It’s boilerplate and the only hole to be filled is the type containing the ways to activate the PAB service, which has defined the handlers. In our case it’s the Escrow type.

main :: IO ()
main = runWith $ handleBuiltin @Escrow