diff --git a/src/main/java/org/qortal/api/resource/CrossChainHtlcResource.java b/src/main/java/org/qortal/api/resource/CrossChainHtlcResource.java
index 0442b2744..98e9b01d0 100644
--- a/src/main/java/org/qortal/api/resource/CrossChainHtlcResource.java
+++ b/src/main/java/org/qortal/api/resource/CrossChainHtlcResource.java
@@ -20,10 +20,7 @@
import org.apache.logging.log4j.Logger;
import org.bitcoinj.core.*;
import org.bitcoinj.script.Script;
-import org.qortal.api.ApiError;
-import org.qortal.api.ApiErrors;
-import org.qortal.api.ApiExceptionFactory;
-import org.qortal.api.Security;
+import org.qortal.api.*;
import org.qortal.api.model.CrossChainBitcoinyHTLCStatus;
import org.qortal.crosschain.*;
import org.qortal.crypto.Crypto;
@@ -279,6 +276,97 @@ public boolean redeemHtlc(@PathParam("ataddress") String atAddress) {
}
}
+ @GET
+ @Path("/redeemAll/LITECOIN")
+ @Operation(
+ summary = "Redeems HTLC for all applicable ATs in tradebot data",
+ description = "To be used by a QORT seller (Bob) who needs to redeem LTC proceeds that are stuck in P2SH transactions.
" +
+ "This requires Bob's trade bot data to be present in the database for any ATs that need redeeming.
" +
+ "Returns true if at least one trade is redeemed. More detail is available in the log.txt.* file.",
+ responses = {
+ @ApiResponse(
+ content = @Content(mediaType = MediaType.TEXT_PLAIN, schema = @Schema(type = "boolean"))
+ )
+ }
+ )
+ @ApiErrors({ApiError.INVALID_CRITERIA, ApiError.INVALID_ADDRESS, ApiError.ADDRESS_UNKNOWN})
+ public boolean redeemAllHtlc() {
+ Security.checkApiCallAllowed(request);
+ boolean success = false;
+
+ try (final Repository repository = RepositoryManager.getRepository()) {
+ List allTradeBotData = repository.getCrossChainRepository().getAllTradeBotData();
+
+ for (TradeBotData tradeBotData : allTradeBotData) {
+ String atAddress = tradeBotData.getAtAddress();
+ if (atAddress == null) {
+ LOGGER.info("Missing AT address in tradebot data", atAddress);
+ continue;
+ }
+
+ String tradeState = tradeBotData.getState();
+ if (tradeState == null) {
+ LOGGER.info("Missing trade state for AT {}", atAddress);
+ continue;
+ }
+
+ if (tradeState.startsWith("ALICE")) {
+ LOGGER.info("AT {} isn't redeemable because it is a buy order", atAddress);
+ continue;
+ }
+
+ ATData atData = repository.getATRepository().fromATAddress(atAddress);
+ if (atData == null) {
+ LOGGER.info("Couldn't find AT with address {}", atAddress);
+ continue;
+ }
+
+ ACCT acct = SupportedBlockchain.getAcctByCodeHash(atData.getCodeHash());
+ if (acct == null) {
+ continue;
+ }
+
+ CrossChainTradeData crossChainTradeData = acct.populateTradeData(repository, atData);
+ if (crossChainTradeData == null) {
+ LOGGER.info("Couldn't find crosschain trade data for AT {}", atAddress);
+ continue;
+ }
+
+ // Attempt to find secret from the buyer's message to AT
+ byte[] decodedSecret = LitecoinACCTv1.findSecretA(repository, crossChainTradeData);
+ if (decodedSecret == null) {
+ LOGGER.info("Unable to find secret-A from redeem message to AT {}", atAddress);
+ continue;
+ }
+
+ // Search for the tradePrivateKey in the tradebot data
+ byte[] decodedPrivateKey = tradeBotData.getTradePrivateKey();
+
+ // Search for the litecoin receiving address PKH in the tradebot data
+ byte[] litecoinReceivingAccountInfo = tradeBotData.getReceivingAccountInfo();
+
+ try {
+ LOGGER.info("Attempting to redeem P2SH balance associated with AT {}...", atAddress);
+ boolean redeemed = this.doRedeemHtlc(atAddress, decodedPrivateKey, decodedSecret, litecoinReceivingAccountInfo);
+ if (redeemed) {
+ LOGGER.info("Redeemed P2SH balance associated with AT {}", atAddress);
+ success = true;
+ }
+ else {
+ LOGGER.info("Couldn't redeem P2SH balance associated with AT {}. Already redeemed?", atAddress);
+ }
+ } catch (ApiException e) {
+ LOGGER.info("Couldn't redeem P2SH balance associated with AT {}. Missing data?", atAddress);
+ }
+ }
+
+ } catch (DataException e) {
+ throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.REPOSITORY_ISSUE, e);
+ }
+
+ return success;
+ }
+
private boolean doRedeemHtlc(String atAddress, byte[] decodedTradePrivateKey, byte[] decodedSecret, byte[] litecoinReceivingAccountInfo) {
try (final Repository repository = RepositoryManager.getRepository()) {
ATData atData = repository.getATRepository().fromATAddress(atAddress);