Update the Backend API

Update the backend Java code to handle the new choice based on the user’s actions in the frontend.

The backend update includes modifications to three existing files within the app/backend subdirectories, api.yaml, Store.java, and UserApiDelegateImpl.java.

Step 1: Modify api.yaml

Define the API endpoint in api.yaml to allow users to retrieve rejected transfer offers. The endpoint is added to api.yaml (located in /app/backend/src/main/openapi/) after the /user/transfer-offers/disclosed-contracts-for-accept endpoint. It bridges the desired business operation with the system’s data and business logic.

123  /user/rejected-transfer-offers:
124    get:
125      security:
126        - BearerAuth: []
127      tags:
128        - read
129      summary: Get rejected transfer offers
130      operationId: getRejectedTransferOffers
131      responses:
132        '200':
133          description: Successfully retrieved rejected transfer offers
134          content:
135            application/json:
136              schema:
137                $ref: '#/components/schemas/ListRejectedTransferOfferResponse'
138        '403':
139          description: Permission denied
140        '500':
141          description: Internal server error

Rejected-transfer-offers is a read operation API endpoint that uses a GET request to retrieve rejected transfer offers. It requires bearer token authentication and can return three types of responses: success (200), permission denied (403), or server error (500).

A success response returns data in a defined JSON format, structured according to the ListRejectedTransferOfferResponse schema. The following schema includes a rejected transfer offer object and an array of items, ensuring a consistent data structure for clients.

Add the schema ListRejectedTransferOfferResponse after the schema GetTransferOfferResponse.

238    ListRejectedTransferOfferResponse:
239      type: object
240      required:
241        - rejected_transfer_offer
242      properties:
243        rejected_transfer_offers:
244          type: array
245          items:
246            $ref: "#/components/schemas/TemplateContract"

The schema ensures that the success response includes a list of rejected transfer offers. Together, the endpoint and schema define how to request and interpret data about rejected transfer offers.

Step 2: Update Store.java

Next, locate Store.java in /app/backend/src/main/java/com/daml/app/template/store.

Import the RejectedTransferOffer template with the existing model imports.

20 import com.daml.app.template.codegen.com.daml.app.template.model.RejectedTransferOffer;

Add a method, listRejectedTransferOffers, near the end of the file, above the listTransientFailureRequests method. listRejectedTransferOffers retrieves transfer offer rejections using the Participant Query Store (PQS).

423   public List<TemplateContract<RejectedTransferOffer.ContractId, RejectedTransferOffer>>
424     listRejectedTransferOffers(String userPartyId) {
425       return pqs.queryTemplates(
426               RejectedTransferOffer.COMPANION,
427               """
428               select contract_id, payload::text, metadata
429                 from active('Com.Daml.App.Template.Model:RejectedTransferOffer')
430                 where payload -> 'transferOffer' ->> 'provider' = ?
431                   and (payload -> 'transferOffer' ->> 'sender' = ? or payload -> 'transferOffer' ->> 'receiver' = ?)
432               """,
433               getProviderParty(),
434               userPartyId,
435               userPartyId)
436           .value();
437     }

The listRejectedTransferOffers method queries PQS for rejected transfer offers. It takes the user’s ID as input and returns a list of TemplateContract objects containing the rejected transfer offer data.

Participant Query Store

PQS operates as a separate service within the participant’s infrastructure. It maintains a mirrored copy of the ledger data in a queryable format. PQS is intended as a long-running process that may be safely restarted at any time. It provides a scalable “read” pipeline that follows the Command Query Responsibility Segregation (CQRS) design pattern, which separates read operations from write operations to improve performance and scalability.

In this extension, PQS fetches and manages relevant rejected transfer offers, enhancing the application’s ability to handle the full lifecycle of transfer offers, including their rejection.

Step 3: Update UserApiDelegateImpl.java

Find UserApiDelegateImpl.java in /app/backend/src/main/java/com/daml/app/template/api/.

Implement the endpoint added under /user/ in api.yaml by delegating rejected transfer offer contract retrieval to Store.java.

First, import TemplateContract and the RejectedTransferOffer template from the Daml model.

4 import com.daml.app.common.TemplateContract;
5 import com.daml.app.template.codegen.com.daml.app.template.model.RejectedTransferOffer;

Import the Collectors utility.

15 import java.util.stream.Collectors;

Then, implement the getRejectedTransferOffers method after the currently existing getTransferOffer method.

119   @Override
120   public ResponseEntity<ListRejectedTransferOfferResponse> getRejectedTransferOffers() {
121     final String userPartyId = getUserPartyId();
122     try {
123       List<TemplateContract<RejectedTransferOffer.ContractId, RejectedTransferOffer>>
124           rejectedOffers = store.listRejectedTransferOffers(userPartyId);
125
126       List<com.daml.app.template.model.TemplateContract> openApiRejectedOffers =
127           rejectedOffers.stream()
128               .map(offer -> offer.toOpenApiServer(objectMapper))
129               .collect(Collectors.toList());
130
131       var response = new ListRejectedTransferOfferResponse();
132       response.setRejectedTransferOffers(openApiRejectedOffers);
133
134       return ResponseEntity.ok(response);
135     } catch (Exception e) {
136       logger.error(
137           "Error fetching rejected transfer offers for user {}: {}", userPartyId, e.getMessage());
138       return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
139     }
140   }

The getRejectedTransferOffers method serves as an interface between the API and the data store.

It calls store.listRejectedTransferOffers to fetch the list of the user’s observable rejected transfer offer contracts from the store. Then, it maps the retrieved rejected offers to the OpenAPI server format, creates a ListRejectedTransferOfferResponse object, and populates the object with mapped offers. Finally, it returns an HTTP response containing the list of rejected transfer offers.

The interface ensures that the API can retrieve and return rejected transfer offers while maintaining consistency with the API specifications defined in api.yaml.

Verify that all changes have been saved, then rebuild the backend.

./gradlew :app:backend:assemble

Gradle generates the backend files with the new API endpoints.

Gradle backend assemble success