diff --git a/core/src/main/java/haveno/core/offer/OpenOffer.java b/core/src/main/java/haveno/core/offer/OpenOffer.java index 1f177959f3..fc4365ecba 100644 --- a/core/src/main/java/haveno/core/offer/OpenOffer.java +++ b/core/src/main/java/haveno/core/offer/OpenOffer.java @@ -110,6 +110,10 @@ public final class OpenOffer implements Tradable { @Getter @Setter transient int numProcessingAttempts = 0; + @Getter + @Setter + private boolean deactivatedByTrigger; + public OpenOffer(Offer offer) { this(offer, 0, false); } @@ -141,6 +145,7 @@ public final class OpenOffer implements Tradable { this.reserveTxHex = openOffer.reserveTxHex; this.reserveTxKey = openOffer.reserveTxKey; this.challenge = openOffer.challenge; + this.deactivatedByTrigger = openOffer.deactivatedByTrigger; } /////////////////////////////////////////////////////////////////////////////////////////// @@ -158,7 +163,8 @@ public final class OpenOffer implements Tradable { @Nullable String reserveTxHash, @Nullable String reserveTxHex, @Nullable String reserveTxKey, - @Nullable String challenge) { + @Nullable String challenge, + boolean deactivatedByTrigger) { this.offer = offer; this.state = state; this.triggerPrice = triggerPrice; @@ -170,6 +176,7 @@ public final class OpenOffer implements Tradable { this.reserveTxHex = reserveTxHex; this.reserveTxKey = reserveTxKey; this.challenge = challenge; + this.deactivatedByTrigger = deactivatedByTrigger; // reset reserved state to available if (this.state == State.RESERVED) setState(State.AVAILABLE); @@ -182,7 +189,8 @@ public final class OpenOffer implements Tradable { .setTriggerPrice(triggerPrice) .setState(protobuf.OpenOffer.State.valueOf(state.name())) .setSplitOutputTxFee(splitOutputTxFee) - .setReserveExactAmount(reserveExactAmount); + .setReserveExactAmount(reserveExactAmount) + .setDeactivatedByTrigger(deactivatedByTrigger); Optional.ofNullable(scheduledAmount).ifPresent(e -> builder.setScheduledAmount(scheduledAmount)); Optional.ofNullable(scheduledTxHashes).ifPresent(e -> builder.addAllScheduledTxHashes(scheduledTxHashes)); @@ -207,7 +215,8 @@ public final class OpenOffer implements Tradable { ProtoUtil.stringOrNullFromProto(proto.getReserveTxHash()), ProtoUtil.stringOrNullFromProto(proto.getReserveTxHex()), ProtoUtil.stringOrNullFromProto(proto.getReserveTxKey()), - ProtoUtil.stringOrNullFromProto(proto.getChallenge())); + ProtoUtil.stringOrNullFromProto(proto.getChallenge()), + proto.getDeactivatedByTrigger()); return openOffer; } @@ -234,6 +243,14 @@ public final class OpenOffer implements Tradable { public void setState(State state) { this.state = state; stateProperty.set(state); + if (state == State.AVAILABLE) { + deactivatedByTrigger = false; + } + } + + public void deactivate(boolean deactivatedByTrigger) { + this.deactivatedByTrigger = deactivatedByTrigger; + setState(State.DEACTIVATED); } public ReadOnlyObjectProperty<State> stateProperty() { diff --git a/core/src/main/java/haveno/core/offer/OpenOfferManager.java b/core/src/main/java/haveno/core/offer/OpenOfferManager.java index 0920c036d7..5bb127f840 100644 --- a/core/src/main/java/haveno/core/offer/OpenOfferManager.java +++ b/core/src/main/java/haveno/core/offer/OpenOfferManager.java @@ -604,13 +604,14 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe } public void deactivateOpenOffer(OpenOffer openOffer, + boolean deactivatedByTrigger, ResultHandler resultHandler, ErrorMessageHandler errorMessageHandler) { Offer offer = openOffer.getOffer(); if (openOffer.isAvailable()) { offerBookService.deactivateOffer(offer.getOfferPayload(), () -> { - openOffer.setState(OpenOffer.State.DEACTIVATED); + openOffer.deactivate(deactivatedByTrigger); requestPersistence(); log.debug("deactivateOpenOffer, offerId={}", offer.getId()); resultHandler.handleResult(); @@ -661,6 +662,7 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe if (openOffer.isAvailable()) { deactivateOpenOffer(openOffer, + false, resultHandler, errorMessage -> { offersToBeEdited.remove(openOffer.getId()); diff --git a/core/src/main/java/haveno/core/offer/TriggerPriceService.java b/core/src/main/java/haveno/core/offer/TriggerPriceService.java index 59705de064..18527b774c 100644 --- a/core/src/main/java/haveno/core/offer/TriggerPriceService.java +++ b/core/src/main/java/haveno/core/offer/TriggerPriceService.java @@ -92,12 +92,11 @@ public class TriggerPriceService { .filter(marketPrice -> openOffersByCurrency.containsKey(marketPrice.getCurrencyCode())) .forEach(marketPrice -> { openOffersByCurrency.get(marketPrice.getCurrencyCode()).stream() - .filter(openOffer -> !openOffer.isDeactivated()) .forEach(openOffer -> checkPriceThreshold(marketPrice, openOffer)); }); } - public static boolean wasTriggered(MarketPrice marketPrice, OpenOffer openOffer) { + public static boolean isTriggered(MarketPrice marketPrice, OpenOffer openOffer) { Price price = openOffer.getOffer().getPrice(); if (price == null || marketPrice == null) { return false; @@ -125,13 +124,12 @@ public class TriggerPriceService { } private void checkPriceThreshold(MarketPrice marketPrice, OpenOffer openOffer) { - if (wasTriggered(marketPrice, openOffer)) { - String currencyCode = openOffer.getOffer().getCurrencyCode(); - int smallestUnitExponent = CurrencyUtil.isTraditionalCurrency(currencyCode) ? - TraditionalMoney.SMALLEST_UNIT_EXPONENT : - CryptoMoney.SMALLEST_UNIT_EXPONENT; - long triggerPrice = openOffer.getTriggerPrice(); + String currencyCode = openOffer.getOffer().getCurrencyCode(); + int smallestUnitExponent = CurrencyUtil.isTraditionalCurrency(currencyCode) ? + TraditionalMoney.SMALLEST_UNIT_EXPONENT : + CryptoMoney.SMALLEST_UNIT_EXPONENT; + if (openOffer.getState() == OpenOffer.State.AVAILABLE && isTriggered(marketPrice, openOffer)) { log.info("Market price exceeded the trigger price of the open offer.\n" + "We deactivate the open offer with ID {}.\nCurrency: {};\nOffer direction: {};\n" + "Market price: {};\nTrigger price: {}", @@ -139,14 +137,26 @@ public class TriggerPriceService { currencyCode, openOffer.getOffer().getDirection(), marketPrice.getPrice(), - MathUtils.scaleDownByPowerOf10(triggerPrice, smallestUnitExponent) + MathUtils.scaleDownByPowerOf10(openOffer.getTriggerPrice(), smallestUnitExponent) ); - openOfferManager.deactivateOpenOffer(openOffer, () -> { + openOfferManager.deactivateOpenOffer(openOffer, true, () -> { + }, errorMessage -> { + }); + } else if (openOffer.getState() == OpenOffer.State.DEACTIVATED && openOffer.isDeactivatedByTrigger() && !isTriggered(marketPrice, openOffer)) { + log.info("Market price is back within the trigger price of the open offer.\n" + + "We reactivate the open offer with ID {}.\nCurrency: {};\nOffer direction: {};\n" + + "Market price: {};\nTrigger price: {}", + openOffer.getOffer().getShortId(), + currencyCode, + openOffer.getOffer().getDirection(), + marketPrice.getPrice(), + MathUtils.scaleDownByPowerOf10(openOffer.getTriggerPrice(), smallestUnitExponent) + ); + + openOfferManager.activateOpenOffer(openOffer, () -> { }, errorMessage -> { }); - } else if (openOffer.getState() == OpenOffer.State.AVAILABLE) { - // TODO: check if open offer's reserve tx is failed or double spend seen } } diff --git a/desktop/src/main/java/haveno/desktop/main/portfolio/openoffer/OpenOffersDataModel.java b/desktop/src/main/java/haveno/desktop/main/portfolio/openoffer/OpenOffersDataModel.java index c1f255d96d..5a178f6dce 100644 --- a/desktop/src/main/java/haveno/desktop/main/portfolio/openoffer/OpenOffersDataModel.java +++ b/desktop/src/main/java/haveno/desktop/main/portfolio/openoffer/OpenOffersDataModel.java @@ -69,7 +69,7 @@ class OpenOffersDataModel extends ActivatableDataModel { } void onDeactivateOpenOffer(OpenOffer openOffer, ResultHandler resultHandler, ErrorMessageHandler errorMessageHandler) { - openOfferManager.deactivateOpenOffer(openOffer, resultHandler, errorMessageHandler); + openOfferManager.deactivateOpenOffer(openOffer, false, resultHandler, errorMessageHandler); } void onRemoveOpenOffer(OpenOffer openOffer, ResultHandler resultHandler, ErrorMessageHandler errorMessageHandler) { @@ -94,7 +94,7 @@ class OpenOffersDataModel extends ActivatableDataModel { list.sort((o1, o2) -> o2.getOffer().getDate().compareTo(o1.getOffer().getDate())); } - boolean wasTriggered(OpenOffer openOffer) { - return TriggerPriceService.wasTriggered(priceFeedService.getMarketPrice(openOffer.getOffer().getCurrencyCode()), openOffer); + boolean isTriggered(OpenOffer openOffer) { + return TriggerPriceService.isTriggered(priceFeedService.getMarketPrice(openOffer.getOffer().getCurrencyCode()), openOffer); } } diff --git a/desktop/src/main/java/haveno/desktop/main/portfolio/openoffer/OpenOffersView.java b/desktop/src/main/java/haveno/desktop/main/portfolio/openoffer/OpenOffersView.java index c1f5566398..575cc1d2b1 100644 --- a/desktop/src/main/java/haveno/desktop/main/portfolio/openoffer/OpenOffersView.java +++ b/desktop/src/main/java/haveno/desktop/main/portfolio/openoffer/OpenOffersView.java @@ -368,7 +368,7 @@ public class OpenOffersView extends ActivatableViewAndModel<VBox, OpenOffersView } private void onActivateOpenOffer(OpenOffer openOffer) { - if (model.isBootstrappedOrShowPopup() && !model.dataModel.wasTriggered(openOffer)) { + if (model.isBootstrappedOrShowPopup() && !model.dataModel.isTriggered(openOffer)) { model.onActivateOpenOffer(openOffer, () -> log.debug("Activate offer was successful"), (message) -> { @@ -720,7 +720,7 @@ public class OpenOffersView extends ActivatableViewAndModel<VBox, OpenOffersView checkBox.setPadding(new Insets(-7, 0, -7, 0)); checkBox.setGraphic(iconView); } - checkBox.setDisable(model.dataModel.wasTriggered(openOffer)); + checkBox.setDisable(model.dataModel.isTriggered(openOffer)); checkBox.setOnAction(event -> { if (openOffer.isDeactivated()) { onActivateOpenOffer(openOffer); @@ -798,7 +798,7 @@ public class OpenOffersView extends ActivatableViewAndModel<VBox, OpenOffersView boolean triggerPriceSet = item.getOpenOffer().getTriggerPrice() > 0; button.setVisible(triggerPriceSet); - if (model.dataModel.wasTriggered(item.getOpenOffer())) { + if (model.dataModel.isTriggered(item.getOpenOffer())) { button.getGraphic().getStyleClass().add("warning"); button.setTooltip(new Tooltip(Res.get("openOffer.triggered"))); } else { diff --git a/proto/src/main/proto/pb.proto b/proto/src/main/proto/pb.proto index 290edb668f..04c32ee8dc 100644 --- a/proto/src/main/proto/pb.proto +++ b/proto/src/main/proto/pb.proto @@ -1421,6 +1421,7 @@ message OpenOffer { string reserve_tx_hex = 10; string reserve_tx_key = 11; string challenge = 12; + bool deactivated_by_trigger = 13; } message Tradable {