TransactionController.java
/*
* UVerify Backend
* Copyright (C) 2025 Fabian Bormann
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package io.uverify.backend.controller;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
import io.swagger.v3.oas.annotations.tags.Tag;
import io.uverify.backend.dto.BuildTransactionRequest;
import io.uverify.backend.dto.BuildTransactionResponse;
import io.uverify.backend.dto.SubmitTransactionRequest;
import io.uverify.backend.enums.BuildStatusCode;
import io.uverify.backend.enums.TransactionType;
import io.uverify.backend.service.TransactionService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@SuppressWarnings("unused")
@RequestMapping("/api/v1/transaction")
@Tag(name = "Transaction Management", description = "Endpoints for building and submitting UVerify certificate transactions to the Cardano blockchain.")
public class TransactionController {
@Autowired
private TransactionService transactionService;
@PostMapping("/build")
@Operation(
summary = "Build a transaction",
description = """
Builds a transaction for the Cardano blockchain based on the provided request. Supports the following transaction types:
- **DEFAULT**: Submits UVerify certificates to the blockchain using the cheapest options. If no state is initialized, it forks a new state from the bootstrap datum with the best service fee conditions. If a user state exists with a valid transaction countdown and no service fee is required, it will be reused.
- **BOOTSTRAP**: Initializes a new bootstrap token and datum for forking states. Requires a whitelisted credential to sign the transaction.
- **CUSTOM**: Allows the user to specify a bootstrap datum to fork or consume a state related to a specific bootstrap datum. This is useful for use cases requiring a 'partner datum' and may result in a different certificate UI on the client side."""
)
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "Transaction built successfully",
content = @Content(mediaType = "application/json", schema = @Schema(implementation = BuildTransactionResponse.class))),
@ApiResponse(responseCode = "400", description = "Invalid transaction type or request data"),
@ApiResponse(responseCode = "500", description = "Internal server error")
})
public ResponseEntity<?> buildTransaction(@RequestBody BuildTransactionRequest request) {
try {
if (request.getType().equals(TransactionType.DEFAULT)) {
BuildTransactionResponse buildTransactionResponse = transactionService.buildUVerifyTransaction(request.getCertificates(), request.getAddress());
if (buildTransactionResponse.getStatus().getCode().equals(BuildStatusCode.SUCCESS)) {
return ResponseEntity.ok(buildTransactionResponse);
} else {
return ResponseEntity.badRequest().body(buildTransactionResponse);
}
} else if (request.getType().equals(TransactionType.BOOTSTRAP)) {
BuildTransactionResponse buildTransactionResponse = transactionService.buildBootstrapDatum(request.getBootstrapDatum());
if (buildTransactionResponse.getStatus().getCode().equals(BuildStatusCode.SUCCESS)) {
return ResponseEntity.ok(buildTransactionResponse);
} else {
return ResponseEntity.badRequest().body(buildTransactionResponse);
}
} else if (request.getType().equals(TransactionType.CUSTOM)) {
BuildTransactionResponse buildTransactionResponse = transactionService.buildCustomTransaction(request.getCertificates(), request.getAddress(), request.getBootstrapDatum().getName());
if (buildTransactionResponse.getStatus().getCode().equals(BuildStatusCode.SUCCESS)) {
return ResponseEntity.ok(buildTransactionResponse);
} else {
return ResponseEntity.badRequest().body(buildTransactionResponse);
}
} else {
return ResponseEntity.badRequest().body("Unknown transaction type. Allowed types are: DEFAULT, BOOTSTRAP, CUSTOM.");
}
} catch (Exception e) {
return ResponseEntity.internalServerError().build();
}
}
@PostMapping("/submit")
@Operation(
summary = "Submit a transaction",
description = "Submits a transaction to the Cardano blockchain using the provided transaction data and witness set. "
+ "Returns the result of the submission or a 500 status code in case of server errors."
)
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "Transaction submitted successfully",
content = @Content(mediaType = "application/json")),
@ApiResponse(responseCode = "500", description = "Internal server error")
})
public ResponseEntity<?> submitTransaction(@RequestBody SubmitTransactionRequest request) {
try {
return ResponseEntity.ok(transactionService.submit(request.getTransaction(), request.getWitnessSet()));
} catch (Exception e) {
return ResponseEntity.internalServerError().build();
}
}
}