3. Contract Endpoints
The ContractEndpoints
module provides a more abstract way to connect to the
PAB, following design patterns we find generally useful for dApps. It is built
on top of the PAB API module.
To be able to use this module, the off-chain code must also follow the design patterns.
3.1. Create a ContractEndpoints instance
The creation of a ContractEndpoints
instance implies the activation of a
contract instance in the PAB. This is done using the connect
constructor.
For instance:
const pabUrl = 'http://localhost:9080/api';
const endpoints = await ContractEndpoints.connect(
pabUrl,
call: {
tag: ... , // string (optional)
contents: ...,
},
);
Here, pabUrl
is the PAB URL, and call
follows the same structure as the
one used in the activate method of PAB API.
3.2. Perform an operation
The main pattern we implement is the process of getting an unbalanced transaction from the PAB, that requires calling and endpoint and then polling the PAB status until the yielded transaction shows up.
For instance, in the escrow example we can call the “resolve” endpoint this way:
const result = await endpoints.doOperation(
{
tag: "resolve", // string
contents: ..., // something of type ResolveParams
}
);
if (failed(result)) {
... // here take a look at result.error
}
const tx = result.value; // of type ExportTx
The method returns an object of type Result<ExportTx>
. The utility type
Result
implements a design pattern for operations that can succeed or fail
without using exceptions. If the call is successful, the transaction together
with its complementary information can be found in the value
attribute, in
an object of type ExportTx
, as explained in section 2.4.1.
Internally, doOperation
is polling the PAB status until a new transaction
shows up in the cicYieldedExportTxs
field. Alternatively, the polling will
stop if an error is logged into the cicCurrentState.logs
list or in the
cicCurrentState.err
field of the PAB status.
Therefore, to avoid infinite polling, the Haskell off-chain code for the
endpoint must be programmed accordingly, by always either yelding a
transaction or logging an error. To log an error, the logError
function can
be used.
3.3. Reload the observable state
Another important pattern we implement is the definition of the reload
endpoint, that only updates the observable state in the PAB status, with no
transaction yielding. This endpoint can be used for performing blockchain
queries and obtaining useful information for the frontend.
In this pattern, it is also required to poll the PAB status after calling the
endpoint. To be able to tell that the observable state has been updated, the
observable state defined in the Haskell off-chain code the must have a
particular structure, including a reloadFlag
integer field. The
reload
endpoint must take this integer as a parameter and set it in the
observable state.
In ContractEnpoints
, the complete process of calling the reload
PAB endpoint and polling the status is implemented as the reload
method.
The reloadFlag
integer is internally managed by the module, and the
reload
method only returns the info
field of the observable state.
For instance, in the escrow example the call is done as follows:
const result = await endpoints.reload();
if (failed(result)) {
... // here take a look at result.error
}
const escrows = result.value as PABObservableState;