From c3c1f95cba6c3294e6660268b0e21a5669d873f2 Mon Sep 17 00:00:00 2001 From: chimp1984 Date: Fri, 1 Jan 2021 22:10:39 -0500 Subject: [PATCH 1/9] Extract methods for show all and edit entries. Use isShowAllEntry methods instead of equals checks --- .../offer/offerbook/OfferBookViewModel.java | 31 +++++++++++++------ 1 file changed, 22 insertions(+), 9 deletions(-) diff --git a/desktop/src/main/java/bisq/desktop/main/offer/offerbook/OfferBookViewModel.java b/desktop/src/main/java/bisq/desktop/main/offer/offerbook/OfferBookViewModel.java index 92b97e1e29b..0a20891ea0c 100644 --- a/desktop/src/main/java/bisq/desktop/main/offer/offerbook/OfferBookViewModel.java +++ b/desktop/src/main/java/bisq/desktop/main/offer/offerbook/OfferBookViewModel.java @@ -122,7 +122,7 @@ class OfferBookViewModel extends ActivatableViewModel { // If id is empty string we ignore filter (display all methods) - PaymentMethod selectedPaymentMethod = PaymentMethod.getDummyPaymentMethod(GUIUtil.SHOW_ALL_FLAG); + PaymentMethod selectedPaymentMethod = getShowAllEntryForPaymentMethod(); private boolean isTabSelected; final BooleanProperty showAllTradeCurrenciesProperty = new SimpleBooleanProperty(true); @@ -213,7 +213,7 @@ protected void activate() { filteredItems.addListener(filterItemsListener); String code = direction == OfferPayload.Direction.BUY ? preferences.getBuyScreenCurrencyCode() : preferences.getSellScreenCurrencyCode(); - if (code != null && !code.equals(GUIUtil.SHOW_ALL_FLAG) && !code.isEmpty() && + if (code != null && !code.isEmpty() && !isShowAllEntry(code) && CurrencyUtil.getTradeCurrency(code).isPresent()) { showAllTradeCurrenciesProperty.set(false); selectedTradeCurrency = CurrencyUtil.getTradeCurrency(code).get(); @@ -288,10 +288,10 @@ void onSetPaymentMethod(PaymentMethod paymentMethod) { // If we select TransferWise we switch to show all currencies as TransferWise supports // sending to most currencies. if (paymentMethod.getId().equals(PaymentMethod.TRANSFERWISE_ID)) { - onSetTradeCurrency(new CryptoCurrency(GUIUtil.SHOW_ALL_FLAG, "")); + onSetTradeCurrency(getShowAllEntryForCurrency()); } } else { - this.selectedPaymentMethod = PaymentMethod.getDummyPaymentMethod(GUIUtil.SHOW_ALL_FLAG); + this.selectedPaymentMethod = getShowAllEntryForPaymentMethod(); } applyFilterPredicate(); @@ -348,7 +348,7 @@ ObservableList getPaymentMethods() { } list.sort(Comparator.naturalOrder()); - list.add(0, PaymentMethod.getDummyPaymentMethod(GUIUtil.SHOW_ALL_FLAG)); + list.add(0, getShowAllEntryForPaymentMethod()); return list; } @@ -528,11 +528,12 @@ private void setMarketPriceFeedCurrency() { private void fillAllTradeCurrencies() { allTradeCurrencies.clear(); // Used for ignoring filter (show all) - allTradeCurrencies.add(new CryptoCurrency(GUIUtil.SHOW_ALL_FLAG, "")); + allTradeCurrencies.add(getShowAllEntryForCurrency()); allTradeCurrencies.addAll(preferences.getTradeCurrenciesAsObservable()); - allTradeCurrencies.add(new CryptoCurrency(GUIUtil.EDIT_FLAG, "")); + allTradeCurrencies.add(getEditEntryForCurrency()); } + /////////////////////////////////////////////////////////////////////////////////////////// // Checks /////////////////////////////////////////////////////////////////////////////////////////// @@ -645,11 +646,11 @@ int getNumTrades(Offer offer) { public boolean hasSelectionAccountSigning() { if (showAllTradeCurrenciesProperty.get()) { - if (!selectedPaymentMethod.getId().equals(GUIUtil.SHOW_ALL_FLAG)) { + if (!isShowAllEntry(selectedPaymentMethod.getId())) { return PaymentMethod.hasChargebackRisk(selectedPaymentMethod); } } else { - if (selectedPaymentMethod.getId().equals(GUIUtil.SHOW_ALL_FLAG)) + if (isShowAllEntry(selectedPaymentMethod.getId())) return CurrencyUtil.getMatureMarketCurrencies().stream() .anyMatch(c -> c.getCode().equals(selectedTradeCurrency.getCode())); else @@ -675,4 +676,16 @@ public String formatDepositString(Coin deposit, long amount) { var percentage = FormattingUtils.formatToRoundedPercentWithSymbol(deposit.getValue() / (double) amount); return btcFormatter.formatCoin(deposit) + " (" + percentage + ")"; } + + private TradeCurrency getShowAllEntryForCurrency() { + return new CryptoCurrency(GUIUtil.SHOW_ALL_FLAG, ""); + } + + private TradeCurrency getEditEntryForCurrency() { + return new CryptoCurrency(GUIUtil.EDIT_FLAG, ""); + } + + private PaymentMethod getShowAllEntryForPaymentMethod() { + return PaymentMethod.getDummyPaymentMethod(GUIUtil.SHOW_ALL_FLAG); + } } From 9e275048f6fed5f10a454c0d4f930055bb59910f Mon Sep 17 00:00:00 2001 From: chimp1984 Date: Sat, 2 Jan 2021 18:36:56 -0500 Subject: [PATCH 2/9] Add toggle for filtering offers which can be taken with users accounts --- .../main/java/bisq/core/user/Preferences.java | 7 ++ .../bisq/core/user/PreferencesPayload.java | 8 +- .../resources/i18n/displayStrings.properties | 1 + .../main/offer/offerbook/OfferBookView.java | 41 ++++--- .../offer/offerbook/OfferBookViewModel.java | 100 ++++++++++++++++-- proto/src/main/proto/pb.proto | 1 + 6 files changed, 134 insertions(+), 24 deletions(-) diff --git a/core/src/main/java/bisq/core/user/Preferences.java b/core/src/main/java/bisq/core/user/Preferences.java index c07a912cb5b..c7080614830 100644 --- a/core/src/main/java/bisq/core/user/Preferences.java +++ b/core/src/main/java/bisq/core/user/Preferences.java @@ -772,6 +772,11 @@ public void setIgnoreDustThreshold(int value) { requestPersistence(); } + public void setShowOffersMatchingMyAccounts(boolean value) { + prefPayload.setShowOffersMatchingMyAccounts(value); + requestPersistence(); + } + /////////////////////////////////////////////////////////////////////////////////////////// // Getter @@ -1081,5 +1086,7 @@ private interface ExcludesDelegateMethods { void setAutoConfirmSettings(AutoConfirmSettings autoConfirmSettings); void setHideNonAccountPaymentMethods(boolean hideNonAccountPaymentMethods); + + void setShowOffersMatchingMyAccounts(boolean value); } } diff --git a/core/src/main/java/bisq/core/user/PreferencesPayload.java b/core/src/main/java/bisq/core/user/PreferencesPayload.java index 82b800f13eb..03628791f36 100644 --- a/core/src/main/java/bisq/core/user/PreferencesPayload.java +++ b/core/src/main/java/bisq/core/user/PreferencesPayload.java @@ -131,6 +131,8 @@ public final class PreferencesPayload implements PersistableEnvelope { // Added in 1.5.5 private boolean hideNonAccountPaymentMethods; + private boolean showOffersMatchingMyAccounts; + /////////////////////////////////////////////////////////////////////////////////////////// // Constructor @@ -195,7 +197,8 @@ public Message toProtoMessage() { .addAllAutoConfirmSettings(autoConfirmSettingsList.stream() .map(autoConfirmSettings -> ((protobuf.AutoConfirmSettings) autoConfirmSettings.toProtoMessage())) .collect(Collectors.toList())) - .setHideNonAccountPaymentMethods(hideNonAccountPaymentMethods); + .setHideNonAccountPaymentMethods(hideNonAccountPaymentMethods) + .setShowOffersMatchingMyAccounts(showOffersMatchingMyAccounts); Optional.ofNullable(backupDirectory).ifPresent(builder::setBackupDirectory); Optional.ofNullable(preferredTradeCurrency).ifPresent(e -> builder.setPreferredTradeCurrency((protobuf.TradeCurrency) e.toProtoMessage())); @@ -290,7 +293,8 @@ public static PreferencesPayload fromProto(protobuf.PreferencesPayload proto, Co new ArrayList<>(proto.getAutoConfirmSettingsList().stream() .map(AutoConfirmSettings::fromProto) .collect(Collectors.toList())), - proto.getHideNonAccountPaymentMethods() + proto.getHideNonAccountPaymentMethods(), + proto.getShowOffersMatchingMyAccounts() ); } } diff --git a/core/src/main/resources/i18n/displayStrings.properties b/core/src/main/resources/i18n/displayStrings.properties index fec863f6746..4af5a97364c 100644 --- a/core/src/main/resources/i18n/displayStrings.properties +++ b/core/src/main/resources/i18n/displayStrings.properties @@ -342,6 +342,7 @@ offerbook.offerersAcceptedBankSeats=Accepted seat of bank countries (taker):\n { offerbook.availableOffers=Available offers offerbook.filterByCurrency=Filter by currency offerbook.filterByPaymentMethod=Filter by payment method +offerbook.matchingOffers=Offers matching my accounts offerbook.timeSinceSigning=Account info offerbook.timeSinceSigning.info=This account was verified and {0} offerbook.timeSinceSigning.info.arbitrator=signed by an arbitrator and can sign peer accounts diff --git a/desktop/src/main/java/bisq/desktop/main/offer/offerbook/OfferBookView.java b/desktop/src/main/java/bisq/desktop/main/offer/offerbook/OfferBookView.java index 417fe20730d..6c3fa3d6c24 100644 --- a/desktop/src/main/java/bisq/desktop/main/offer/offerbook/OfferBookView.java +++ b/desktop/src/main/java/bisq/desktop/main/offer/offerbook/OfferBookView.java @@ -22,6 +22,7 @@ import bisq.desktop.common.view.FxmlView; import bisq.desktop.components.AutoTooltipButton; import bisq.desktop.components.AutoTooltipLabel; +import bisq.desktop.components.AutoTooltipSlideToggleButton; import bisq.desktop.components.AutoTooltipTableColumn; import bisq.desktop.components.AutocompleteComboBox; import bisq.desktop.components.ColoredDecimalPlacesWithZerosText; @@ -126,6 +127,7 @@ public class OfferBookView extends ActivatableViewAndModel currencyComboBox; private AutocompleteComboBox paymentMethodComboBox; private AutoTooltipButton createOfferButton; + private AutoTooltipSlideToggleButton matchingOffersToggle; private AutoTooltipTableColumn amountColumn, volumeColumn, marketColumn, priceColumn, paymentMethodColumn, depositColumn, signingStateColumn, avatarColumn; private TableView tableView; @@ -174,10 +176,25 @@ public void initialize() { hBox.setSpacing(35); hBox.setPadding(new Insets(10, 0, 0, 0)); - final Tuple3> currencyBoxTuple = FormBuilder.addTopLabelAutocompleteComboBox( + Tuple3> currencyBoxTuple = FormBuilder.addTopLabelAutocompleteComboBox( Res.get("offerbook.filterByCurrency")); - final Tuple3> paymentBoxTuple = FormBuilder.addTopLabelAutocompleteComboBox( + currencyComboBox = currencyBoxTuple.third; + currencyComboBox.setPrefWidth(270); + + Tuple3> paymentBoxTuple = FormBuilder.addTopLabelAutocompleteComboBox( Res.get("offerbook.filterByPaymentMethod")); + paymentMethodComboBox = paymentBoxTuple.third; + paymentMethodComboBox.setCellFactory(GUIUtil.getPaymentMethodCellFactory()); + paymentMethodComboBox.setPrefWidth(270); + + matchingOffersToggle = new AutoTooltipSlideToggleButton(); + matchingOffersToggle.setText(Res.get("offerbook.matchingOffers")); + HBox.setMargin(matchingOffersToggle, new Insets(7, 0, -9, -15)); + + hBox.getChildren().addAll(currencyBoxTuple.first, paymentBoxTuple.first, matchingOffersToggle); + AnchorPane.setLeftAnchor(hBox, 0d); + AnchorPane.setTopAnchor(hBox, 0d); + AnchorPane.setBottomAnchor(hBox, 0d); createOfferButton = new AutoTooltipButton(); createOfferButton.setMinHeight(40); @@ -185,11 +202,6 @@ public void initialize() { AnchorPane.setRightAnchor(createOfferButton, 0d); AnchorPane.setBottomAnchor(createOfferButton, 0d); - hBox.getChildren().addAll(currencyBoxTuple.first, paymentBoxTuple.first, createOfferButton); - AnchorPane.setLeftAnchor(hBox, 0d); - AnchorPane.setTopAnchor(hBox, 0d); - AnchorPane.setBottomAnchor(hBox, 0d); - AnchorPane anchorPane = new AnchorPane(); anchorPane.getChildren().addAll(hBox, createOfferButton); @@ -199,11 +211,6 @@ public void initialize() { GridPane.setMargin(anchorPane, new Insets(Layout.FIRST_ROW_DISTANCE, 0, 0, 0)); root.getChildren().add(anchorPane); - currencyComboBox = currencyBoxTuple.third; - - paymentMethodComboBox = paymentBoxTuple.third; - paymentMethodComboBox.setCellFactory(GUIUtil.getPaymentMethodCellFactory()); - tableView = new TableView<>(); GridPane.setRowIndex(tableView, ++gridRow); @@ -328,6 +335,10 @@ protected void activate() { currencyComboBox.getEditor().setText(new CurrencyStringConverter(currencyComboBox).toString(currencyComboBox.getSelectionModel().getSelectedItem())); + matchingOffersToggle.setSelected(model.useOffersMatchingMyAccountsFilter); + matchingOffersToggle.disableProperty().bind(model.disableMatchToggle); + matchingOffersToggle.setOnAction(e -> model.onShowOffersMatchingMyAccounts(matchingOffersToggle.isSelected())); + volumeColumn.sortableProperty().bind(model.showAllTradeCurrenciesProperty.not()); model.getOfferList().comparatorProperty().bind(tableView.comparatorProperty()); @@ -424,6 +435,8 @@ private void updateSigningStateColumn() { @Override protected void deactivate() { createOfferButton.setOnAction(null); + matchingOffersToggle.setOnAction(null); + matchingOffersToggle.disableProperty().unbind(); model.getOfferList().comparatorProperty().unbind(); volumeColumn.sortableProperty().unbind(); @@ -1024,6 +1037,10 @@ public void updateItem(final OfferBookListItem item, boolean empty) { final Offer offer = item.getOffer(); boolean myOffer = model.isMyOffer(offer); if (tableRow != null) { + // this code is duplicated in model.getOffersMatchingMyAccountsPredicate but as + // we want to pass the results for displaying relevant info in popups we + // cannot simply replace it with the predicate. If there are any changes we + // need to maintain both. isPaymentAccountValidForOffer = model.isAnyPaymentAccountValidForOffer(offer); isInsufficientCounterpartyTradeLimit = model.isInsufficientCounterpartyTradeLimit(offer); hasSameProtocolVersion = model.hasSameProtocolVersion(offer); diff --git a/desktop/src/main/java/bisq/desktop/main/offer/offerbook/OfferBookViewModel.java b/desktop/src/main/java/bisq/desktop/main/offer/offerbook/OfferBookViewModel.java index 0a20891ea0c..0e9f42b738d 100644 --- a/desktop/src/main/java/bisq/desktop/main/offer/offerbook/OfferBookViewModel.java +++ b/desktop/src/main/java/bisq/desktop/main/offer/offerbook/OfferBookViewModel.java @@ -78,16 +78,19 @@ import javafx.collections.FXCollections; import javafx.collections.ListChangeListener; import javafx.collections.ObservableList; +import javafx.collections.SetChangeListener; import javafx.collections.transformation.FilteredList; import javafx.collections.transformation.SortedList; import java.text.DecimalFormat; import java.util.Comparator; +import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.Set; +import java.util.function.Predicate; import java.util.stream.Collectors; import lombok.extern.slf4j.Slf4j; @@ -126,11 +129,15 @@ class OfferBookViewModel extends ActivatableViewModel { private boolean isTabSelected; final BooleanProperty showAllTradeCurrenciesProperty = new SimpleBooleanProperty(true); + final BooleanProperty disableMatchToggle = new SimpleBooleanProperty(); final IntegerProperty maxPlacesForAmount = new SimpleIntegerProperty(); final IntegerProperty maxPlacesForVolume = new SimpleIntegerProperty(); final IntegerProperty maxPlacesForPrice = new SimpleIntegerProperty(); final IntegerProperty maxPlacesForMarketPriceMargin = new SimpleIntegerProperty(); boolean showAllPaymentMethods = true; + boolean useOffersMatchingMyAccountsFilter; + private final Map myInsufficientTradeLimitCache = new HashMap<>(); + private final Map insufficientCounterpartyTradeLimitCache = new HashMap<>(); /////////////////////////////////////////////////////////////////////////////////////////// @@ -206,6 +213,10 @@ public OfferBookViewModel(User user, highestMarketPriceMarginOffer.ifPresent(offerBookListItem -> maxPlacesForMarketPriceMargin.set(formatMarketPriceMargin(offerBookListItem.getOffer(), false).length())); }; + + // If our accounts have changed we reset our myInsufficientTradeLimitCache as it depends on account data + user.getPaymentAccountsAsObservable().addListener((SetChangeListener) c -> + myInsufficientTradeLimitCache.clear()); } @Override @@ -223,10 +234,13 @@ protected void activate() { } tradeCurrencyCode.set(selectedTradeCurrency.getCode()); + disableMatchToggle.set(user.getPaymentAccounts() == null || user.getPaymentAccounts().isEmpty()); + useOffersMatchingMyAccountsFilter = !disableMatchToggle.get() && isShowOffersMatchingMyAccounts(); + fillAllTradeCurrencies(); preferences.getTradeCurrenciesAsObservable().addListener(tradeCurrencyListChangeListener); offerBook.fillOfferBookListItems(); - applyFilterPredicate(); + filterOffers(); setMarketPriceFeedCurrency(); priceUtil.recalculateBsq30DayAveragePrice(); @@ -238,6 +252,7 @@ protected void deactivate() { preferences.getTradeCurrenciesAsObservable().removeListener(tradeCurrencyListChangeListener); } + /////////////////////////////////////////////////////////////////////////////////////////// // API /////////////////////////////////////////////////////////////////////////////////////////// @@ -268,7 +283,7 @@ else if (!showAllEntry) { } setMarketPriceFeedCurrency(); - applyFilterPredicate(); + filterOffers(); if (direction == OfferPayload.Direction.BUY) preferences.setBuyScreenCurrencyCode(code); @@ -294,17 +309,28 @@ void onSetPaymentMethod(PaymentMethod paymentMethod) { this.selectedPaymentMethod = getShowAllEntryForPaymentMethod(); } - applyFilterPredicate(); + filterOffers(); } void onRemoveOpenOffer(Offer offer, ResultHandler resultHandler, ErrorMessageHandler errorMessageHandler) { openOfferManager.removeOffer(offer, resultHandler, errorMessageHandler); } + void onShowOffersMatchingMyAccounts(boolean isSelected) { + useOffersMatchingMyAccountsFilter = isSelected; + preferences.setShowOffersMatchingMyAccounts(useOffersMatchingMyAccountsFilter); + filterOffers(); + } + + /////////////////////////////////////////////////////////////////////////////////////////// // Getters /////////////////////////////////////////////////////////////////////////////////////////// + boolean isShowOffersMatchingMyAccounts() { + return preferences.isShowOffersMatchingMyAccounts(); + } + SortedList getOfferList() { return sortedItems; } @@ -561,8 +587,15 @@ boolean canCreateOrTakeOffer() { // Filters /////////////////////////////////////////////////////////////////////////////////////////// - private void applyFilterPredicate() { - filteredItems.setPredicate(offerBookListItem -> { + private void filterOffers() { + Predicate predicate = useOffersMatchingMyAccountsFilter ? + getCurrencyAndMethodPredicate().and(getOffersMatchingMyAccountsPredicate()) : + getCurrencyAndMethodPredicate(); + filteredItems.setPredicate(predicate); + } + + private Predicate getCurrencyAndMethodPredicate() { + return offerBookListItem -> { Offer offer = offerBookListItem.getOffer(); boolean directionResult = offer.getDirection() != direction; boolean currencyResult = (showAllTradeCurrenciesProperty.get()) || @@ -571,7 +604,37 @@ private void applyFilterPredicate() { offer.getPaymentMethod().equals(selectedPaymentMethod); boolean notMyOfferOrShowMyOffersActivated = !isMyOffer(offerBookListItem.getOffer()) || preferences.isShowOwnOffersInOfferBook(); return directionResult && currencyResult && paymentMethodResult && notMyOfferOrShowMyOffersActivated; - }); + }; + } + + private Predicate getOffersMatchingMyAccountsPredicate() { + // This code duplicates code in the view at the button column. We need there the different results for + // display in popups so we cannot replace that with the predicate. Any change need to be applied in both + // places. + return offerBookListItem -> { + Offer offer = offerBookListItem.getOffer(); + boolean isPaymentAccountValidForOffer = isAnyPaymentAccountValidForOffer(offer); + boolean isInsufficientCounterpartyTradeLimit = isInsufficientCounterpartyTradeLimit(offer); + boolean hasSameProtocolVersion = hasSameProtocolVersion(offer); + boolean isIgnored = isIgnored(offer); + boolean isOfferBanned = isOfferBanned(offer); + boolean isCurrencyBanned = isCurrencyBanned(offer); + boolean isPaymentMethodBanned = isPaymentMethodBanned(offer); + boolean isNodeAddressBanned = isNodeAddressBanned(offer); + boolean requireUpdateToNewVersion = requireUpdateToNewVersion(); + boolean isMyInsufficientTradeLimit = isMyInsufficientTradeLimit(offer); + boolean isTradable = isPaymentAccountValidForOffer && + !isInsufficientCounterpartyTradeLimit && + hasSameProtocolVersion && + !isIgnored && + !isOfferBanned && + !isCurrencyBanned && + !isPaymentMethodBanned && + !isNodeAddressBanned && + !requireUpdateToNewVersion && + !isMyInsufficientTradeLimit; + return isTradable; + }; } boolean isIgnored(Offer offer) { @@ -599,13 +662,28 @@ boolean requireUpdateToNewVersion() { return filterManager.requireUpdateToNewVersionForTrading(); } + // This call is a bit expensive so we cache results boolean isInsufficientCounterpartyTradeLimit(Offer offer) { - return CurrencyUtil.isFiatCurrency(offer.getCurrencyCode()) && - !accountAgeWitnessService.verifyPeersTradeAmount(offer, offer.getAmount(), errorMessage -> { - }); + String offerId = offer.getId(); + if (insufficientCounterpartyTradeLimitCache.containsKey(offerId)) { + return insufficientCounterpartyTradeLimitCache.get(offerId); + } + + boolean result = CurrencyUtil.isFiatCurrency(offer.getCurrencyCode()) && + !accountAgeWitnessService.verifyPeersTradeAmount(offer, offer.getAmount(), + errorMessage -> { + }); + insufficientCounterpartyTradeLimitCache.put(offerId, result); + return result; } + // This call is a bit expensive so we cache results boolean isMyInsufficientTradeLimit(Offer offer) { + String offerId = offer.getId(); + if (myInsufficientTradeLimitCache.containsKey(offerId)) { + return myInsufficientTradeLimitCache.get(offerId); + } + Optional accountOptional = getMostMaturePaymentAccountForOffer(offer); long myTradeLimit = accountOptional .map(paymentAccount -> accountAgeWitnessService.getMyTradeLimit(paymentAccount, @@ -616,9 +694,11 @@ boolean isMyInsufficientTradeLimit(Offer offer) { accountOptional.isPresent() ? accountOptional.get().getAccountName() : "null", Coin.valueOf(myTradeLimit).toFriendlyString(), Coin.valueOf(offerMinAmount).toFriendlyString()); - return CurrencyUtil.isFiatCurrency(offer.getCurrencyCode()) && + boolean result = CurrencyUtil.isFiatCurrency(offer.getCurrencyCode()) && accountOptional.isPresent() && myTradeLimit < offerMinAmount; + myInsufficientTradeLimitCache.put(offerId, result); + return result; } boolean hasSameProtocolVersion(Offer offer) { diff --git a/proto/src/main/proto/pb.proto b/proto/src/main/proto/pb.proto index 7d7870c5c64..300e9d2bf8f 100644 --- a/proto/src/main/proto/pb.proto +++ b/proto/src/main/proto/pb.proto @@ -1615,6 +1615,7 @@ message PreferencesPayload { repeated AutoConfirmSettings auto_confirm_settings = 56; double bsq_average_trim_threshold = 57; bool hide_non_account_payment_methods = 58; + bool show_offers_matching_my_accounts = 59; } message AutoConfirmSettings { From f588b226201a7ad0834dc9c46211dc7633c739be Mon Sep 17 00:00:00 2001 From: chimp1984 Date: Sat, 2 Jan 2021 19:22:29 -0500 Subject: [PATCH 3/9] Add null checks --- .../main/offer/offerbook/OfferBookViewModel.java | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/desktop/src/main/java/bisq/desktop/main/offer/offerbook/OfferBookViewModel.java b/desktop/src/main/java/bisq/desktop/main/offer/offerbook/OfferBookViewModel.java index 0e9f42b738d..2a69f863fca 100644 --- a/desktop/src/main/java/bisq/desktop/main/offer/offerbook/OfferBookViewModel.java +++ b/desktop/src/main/java/bisq/desktop/main/offer/offerbook/OfferBookViewModel.java @@ -215,8 +215,10 @@ public OfferBookViewModel(User user, }; // If our accounts have changed we reset our myInsufficientTradeLimitCache as it depends on account data - user.getPaymentAccountsAsObservable().addListener((SetChangeListener) c -> - myInsufficientTradeLimitCache.clear()); + if (user != null) { + user.getPaymentAccountsAsObservable().addListener((SetChangeListener) c -> + myInsufficientTradeLimitCache.clear()); + } } @Override @@ -234,7 +236,9 @@ protected void activate() { } tradeCurrencyCode.set(selectedTradeCurrency.getCode()); - disableMatchToggle.set(user.getPaymentAccounts() == null || user.getPaymentAccounts().isEmpty()); + if (user != null) { + disableMatchToggle.set(user.getPaymentAccounts() == null || user.getPaymentAccounts().isEmpty()); + } useOffersMatchingMyAccountsFilter = !disableMatchToggle.get() && isShowOffersMatchingMyAccounts(); fillAllTradeCurrencies(); From 4bbc394b2d91cca83366191fadf5569e784700a2 Mon Sep 17 00:00:00 2001 From: chimp1984 Date: Mon, 4 Jan 2021 11:44:50 -0500 Subject: [PATCH 4/9] Add disableApi flag to filter --- .../main/java/bisq/core/filter/Filter.java | 24 +++++++++++++------ .../main/overlays/windows/FilterWindow.java | 6 ++++- proto/src/main/proto/pb.proto | 1 + 3 files changed, 23 insertions(+), 8 deletions(-) diff --git a/core/src/main/java/bisq/core/filter/Filter.java b/core/src/main/java/bisq/core/filter/Filter.java index a3418475ca9..b433de9476e 100644 --- a/core/src/main/java/bisq/core/filter/Filter.java +++ b/core/src/main/java/bisq/core/filter/Filter.java @@ -94,6 +94,7 @@ public final class Filter implements ProtectedStoragePayload, ExpirablePayload { // added at v1.5.5 private final Set nodeAddressesBannedFromNetwork; + private final boolean disableApi; // After we have created the signature from the filter data we clone it and apply the signature static Filter cloneWithSig(Filter filter, String signatureAsBase64) { @@ -122,7 +123,8 @@ static Filter cloneWithSig(Filter filter, String signatureAsBase64) { filter.getBannedPrivilegedDevPubKeys(), filter.isDisableAutoConf(), filter.getBannedAutoConfExplorers(), - filter.getNodeAddressesBannedFromNetwork()); + filter.getNodeAddressesBannedFromNetwork(), + filter.isDisableApi()); } // Used for signature verification as we created the sig without the signatureAsBase64 field we set it to null again @@ -152,7 +154,8 @@ static Filter cloneWithoutSig(Filter filter) { filter.getBannedPrivilegedDevPubKeys(), filter.isDisableAutoConf(), filter.getBannedAutoConfExplorers(), - filter.getNodeAddressesBannedFromNetwork()); + filter.getNodeAddressesBannedFromNetwork(), + filter.isDisableApi()); } public Filter(List bannedOfferIds, @@ -177,7 +180,8 @@ public Filter(List bannedOfferIds, List bannedPrivilegedDevPubKeys, boolean disableAutoConf, List bannedAutoConfExplorers, - Set nodeAddressesBannedFromNetwork) { + Set nodeAddressesBannedFromNetwork, + boolean disableApi) { this(bannedOfferIds, nodeAddressesBannedFromTrading, bannedPaymentAccounts, @@ -203,7 +207,8 @@ public Filter(List bannedOfferIds, bannedPrivilegedDevPubKeys, disableAutoConf, bannedAutoConfExplorers, - nodeAddressesBannedFromNetwork); + nodeAddressesBannedFromNetwork, + disableApi); } @@ -237,7 +242,8 @@ public Filter(List bannedOfferIds, List bannedPrivilegedDevPubKeys, boolean disableAutoConf, List bannedAutoConfExplorers, - Set nodeAddressesBannedFromNetwork) { + Set nodeAddressesBannedFromNetwork, + boolean disableApi) { this.bannedOfferIds = bannedOfferIds; this.nodeAddressesBannedFromTrading = nodeAddressesBannedFromTrading; this.bannedPaymentAccounts = bannedPaymentAccounts; @@ -264,6 +270,7 @@ public Filter(List bannedOfferIds, this.disableAutoConf = disableAutoConf; this.bannedAutoConfExplorers = bannedAutoConfExplorers; this.nodeAddressesBannedFromNetwork = nodeAddressesBannedFromNetwork; + this.disableApi = disableApi; // ownerPubKeyBytes can be null when called from tests if (ownerPubKeyBytes != null) { @@ -302,7 +309,8 @@ public protobuf.StoragePayload toProtoMessage() { .addAllBannedPrivilegedDevPubKeys(bannedPrivilegedDevPubKeys) .setDisableAutoConf(disableAutoConf) .addAllBannedAutoConfExplorers(bannedAutoConfExplorers) - .addAllNodeAddressesBannedFromNetwork(nodeAddressesBannedFromNetwork); + .addAllNodeAddressesBannedFromNetwork(nodeAddressesBannedFromNetwork) + .setDisableApi(disableApi); Optional.ofNullable(signatureAsBase64).ifPresent(builder::setSignatureAsBase64); Optional.ofNullable(extraDataMap).ifPresent(builder::putAllExtraData); @@ -341,7 +349,8 @@ public static Filter fromProto(protobuf.Filter proto) { ProtoUtil.protocolStringListToList(proto.getBannedPrivilegedDevPubKeysList()), proto.getDisableAutoConf(), ProtoUtil.protocolStringListToList(proto.getBannedAutoConfExplorersList()), - ProtoUtil.protocolStringListToSet(proto.getNodeAddressesBannedFromNetworkList()) + ProtoUtil.protocolStringListToSet(proto.getNodeAddressesBannedFromNetworkList()), + proto.getDisableApi() ); } @@ -385,6 +394,7 @@ public String toString() { ",\n ownerPubKey=" + ownerPubKey + ",\n disableAutoConf=" + disableAutoConf + ",\n nodeAddressesBannedFromNetwork=" + nodeAddressesBannedFromNetwork + + ",\n disableApi=" + disableApi + "\n}"; } } diff --git a/desktop/src/main/java/bisq/desktop/main/overlays/windows/FilterWindow.java b/desktop/src/main/java/bisq/desktop/main/overlays/windows/FilterWindow.java index 3b3d2017114..5ff3f216eab 100644 --- a/desktop/src/main/java/bisq/desktop/main/overlays/windows/FilterWindow.java +++ b/desktop/src/main/java/bisq/desktop/main/overlays/windows/FilterWindow.java @@ -169,6 +169,8 @@ private void addContent() { Res.get("filterWindow.bannedPrivilegedDevPubKeys")).second; InputTextField autoConfExplorersTF = addTopLabelInputTextField(gridPane, ++rowIndex, Res.get("filterWindow.autoConfExplorers")).second; + CheckBox disableApiCheckBox = addLabelCheckBox(gridPane, ++rowIndex, + Res.get("filterWindow.disableApi")); Filter filter = filterManager.getDevFilter(); if (filter != null) { @@ -194,6 +196,7 @@ private void addContent() { disableAutoConfCheckBox.setSelected(filter.isDisableAutoConf()); disableDaoBelowVersionTF.setText(filter.getDisableDaoBelowVersion()); disableTradeBelowVersionTF.setText(filter.getDisableTradeBelowVersion()); + disableApiCheckBox.setSelected(filter.isDisableApi()); } Button removeFilterMessageButton = new AutoTooltipButton(Res.get("filterWindow.remove")); @@ -227,7 +230,8 @@ private void addContent() { readAsList(bannedPrivilegedDevPubKeysTF), disableAutoConfCheckBox.isSelected(), readAsList(autoConfExplorersTF), - new HashSet<>(readAsList(bannedFromNetworkTF)) + new HashSet<>(readAsList(bannedFromNetworkTF)), + disableApiCheckBox.isSelected() ); // We remove first the old filter diff --git a/proto/src/main/proto/pb.proto b/proto/src/main/proto/pb.proto index 7d7870c5c64..65c2ea8ae09 100644 --- a/proto/src/main/proto/pb.proto +++ b/proto/src/main/proto/pb.proto @@ -678,6 +678,7 @@ message Filter { bool disable_auto_conf = 24; repeated string banned_auto_conf_explorers = 25; repeated string node_addresses_banned_from_network = 26; + bool disable_api = 27; } // Deprecated From b5af6bcfc74e2237fed0eece80d74096df006f75 Mon Sep 17 00:00:00 2001 From: chimp1984 Date: Mon, 4 Jan 2021 11:59:00 -0500 Subject: [PATCH 5/9] Add missing Filter params in tests --- core/src/test/java/bisq/core/user/UserPayloadModelVOTest.java | 3 ++- core/src/test/java/bisq/core/util/FeeReceiverSelectorTest.java | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/core/src/test/java/bisq/core/user/UserPayloadModelVOTest.java b/core/src/test/java/bisq/core/user/UserPayloadModelVOTest.java index 4b7f6e3f79e..bc18d6e2c4b 100644 --- a/core/src/test/java/bisq/core/user/UserPayloadModelVOTest.java +++ b/core/src/test/java/bisq/core/user/UserPayloadModelVOTest.java @@ -67,7 +67,8 @@ public void testRoundtripFull() { null, false, Lists.newArrayList(), - new HashSet<>())); + new HashSet<>(), + false)); vo.setRegisteredArbitrator(ArbitratorTest.getArbitratorMock()); vo.setRegisteredMediator(MediatorTest.getMediatorMock()); diff --git a/core/src/test/java/bisq/core/util/FeeReceiverSelectorTest.java b/core/src/test/java/bisq/core/util/FeeReceiverSelectorTest.java index d605e01efc5..731dcfb6cc4 100644 --- a/core/src/test/java/bisq/core/util/FeeReceiverSelectorTest.java +++ b/core/src/test/java/bisq/core/util/FeeReceiverSelectorTest.java @@ -125,6 +125,7 @@ private static Filter filterWithReceivers(List btcFeeReceiverAddresses) null, false, Lists.newArrayList(), - new HashSet<>()); + new HashSet<>(), + false); } } From 95063b6c7f00bef7437c8a2fff83d9ff1351add5 Mon Sep 17 00:00:00 2001 From: chimp1984 Date: Mon, 4 Jan 2021 12:00:29 -0500 Subject: [PATCH 6/9] Add denyApiTaker field to Preferences --- core/src/main/java/bisq/core/user/Preferences.java | 7 +++++++ .../src/main/java/bisq/core/user/PreferencesPayload.java | 8 +++++--- .../main/settings/preferences/PreferencesView.java | 9 +++++++-- proto/src/main/proto/pb.proto | 1 + 4 files changed, 20 insertions(+), 5 deletions(-) diff --git a/core/src/main/java/bisq/core/user/Preferences.java b/core/src/main/java/bisq/core/user/Preferences.java index c7080614830..ee276d0ef03 100644 --- a/core/src/main/java/bisq/core/user/Preferences.java +++ b/core/src/main/java/bisq/core/user/Preferences.java @@ -777,6 +777,11 @@ public void setShowOffersMatchingMyAccounts(boolean value) { requestPersistence(); } + public void setDenyApiTaker(boolean value) { + prefPayload.setDenyApiTaker(value); + requestPersistence(); + } + /////////////////////////////////////////////////////////////////////////////////////////// // Getter @@ -1088,5 +1093,7 @@ private interface ExcludesDelegateMethods { void setHideNonAccountPaymentMethods(boolean hideNonAccountPaymentMethods); void setShowOffersMatchingMyAccounts(boolean value); + + void setDenyApiTaker(boolean value); } } diff --git a/core/src/main/java/bisq/core/user/PreferencesPayload.java b/core/src/main/java/bisq/core/user/PreferencesPayload.java index 03628791f36..b8991f8cbae 100644 --- a/core/src/main/java/bisq/core/user/PreferencesPayload.java +++ b/core/src/main/java/bisq/core/user/PreferencesPayload.java @@ -132,7 +132,7 @@ public final class PreferencesPayload implements PersistableEnvelope { // Added in 1.5.5 private boolean hideNonAccountPaymentMethods; private boolean showOffersMatchingMyAccounts; - + private boolean denyApiTaker; /////////////////////////////////////////////////////////////////////////////////////////// // Constructor @@ -198,7 +198,8 @@ public Message toProtoMessage() { .map(autoConfirmSettings -> ((protobuf.AutoConfirmSettings) autoConfirmSettings.toProtoMessage())) .collect(Collectors.toList())) .setHideNonAccountPaymentMethods(hideNonAccountPaymentMethods) - .setShowOffersMatchingMyAccounts(showOffersMatchingMyAccounts); + .setShowOffersMatchingMyAccounts(showOffersMatchingMyAccounts) + .setDenyApiTaker(denyApiTaker); Optional.ofNullable(backupDirectory).ifPresent(builder::setBackupDirectory); Optional.ofNullable(preferredTradeCurrency).ifPresent(e -> builder.setPreferredTradeCurrency((protobuf.TradeCurrency) e.toProtoMessage())); @@ -294,7 +295,8 @@ public static PreferencesPayload fromProto(protobuf.PreferencesPayload proto, Co .map(AutoConfirmSettings::fromProto) .collect(Collectors.toList())), proto.getHideNonAccountPaymentMethods(), - proto.getShowOffersMatchingMyAccounts() + proto.getShowOffersMatchingMyAccounts(), + proto.getDenyApiTaker() ); } } diff --git a/desktop/src/main/java/bisq/desktop/main/settings/preferences/PreferencesView.java b/desktop/src/main/java/bisq/desktop/main/settings/preferences/PreferencesView.java index 79870291161..19f4b736071 100644 --- a/desktop/src/main/java/bisq/desktop/main/settings/preferences/PreferencesView.java +++ b/desktop/src/main/java/bisq/desktop/main/settings/preferences/PreferencesView.java @@ -120,7 +120,7 @@ public class PreferencesView extends ActivatableViewAndModel preferredTradeCurrencyComboBox; private ToggleButton showOwnOffersInOfferBook, useAnimations, useDarkMode, sortMarketCurrenciesNumerically, - avoidStandbyMode, useCustomFee, autoConfirmXmrToggle, hideNonAccountPaymentMethodsToggle; + avoidStandbyMode, useCustomFee, autoConfirmXmrToggle, hideNonAccountPaymentMethodsToggle, denyApiTakerToggle; private int gridRow = 0; private int displayCurrenciesGridRowIndex = 0; private InputTextField transactionFeeInputTextField, ignoreTradersListInputTextField, ignoreDustThresholdInputTextField, @@ -603,7 +603,7 @@ public CryptoCurrency fromString(String s) { } private void initializeDisplayOptions() { - TitledGroupBg titledGroupBg = addTitledGroupBg(root, ++gridRow, 6, Res.get("setting.preferences.displayOptions"), Layout.GROUP_DISTANCE); + TitledGroupBg titledGroupBg = addTitledGroupBg(root, ++gridRow, 7, Res.get("setting.preferences.displayOptions"), Layout.GROUP_DISTANCE); GridPane.setColumnSpan(titledGroupBg, 1); showOwnOffersInOfferBook = addSlideToggleButton(root, gridRow, Res.get("setting.preferences.showOwnOffers"), Layout.FIRST_ROW_AND_GROUP_DISTANCE); @@ -611,6 +611,7 @@ private void initializeDisplayOptions() { useDarkMode = addSlideToggleButton(root, ++gridRow, Res.get("setting.preferences.useDarkMode")); sortMarketCurrenciesNumerically = addSlideToggleButton(root, ++gridRow, Res.get("setting.preferences.sortWithNumOffers")); hideNonAccountPaymentMethodsToggle = addSlideToggleButton(root, ++gridRow, Res.get("setting.preferences.onlyShowPaymentMethodsFromAccount")); + denyApiTakerToggle = addSlideToggleButton(root, ++gridRow, Res.get("setting.preferences.denyApiTaker")); resetDontShowAgainButton = addButton(root, ++gridRow, Res.get("setting.preferences.resetAllFlags"), 0); resetDontShowAgainButton.getStyleClass().add("compact-button"); resetDontShowAgainButton.setMaxWidth(Double.MAX_VALUE); @@ -950,6 +951,9 @@ private void activateDisplayPreferences() { hideNonAccountPaymentMethodsToggle.setOnAction(e -> preferences.setHideNonAccountPaymentMethods(hideNonAccountPaymentMethodsToggle.isSelected())); hideNonAccountPaymentMethodsToggle.setDisable(disableToggle); + denyApiTakerToggle.setSelected(preferences.isDenyApiTaker()); + denyApiTakerToggle.setOnAction(e -> preferences.setDenyApiTaker(denyApiTakerToggle.isSelected())); + resetDontShowAgainButton.setOnAction(e -> preferences.resetDontShowAgain()); editCustomBtcExplorer.setOnAction(e -> { @@ -1128,6 +1132,7 @@ private void deactivateDisplayPreferences() { useDarkMode.setOnAction(null); sortMarketCurrenciesNumerically.setOnAction(null); hideNonAccountPaymentMethodsToggle.setOnAction(null); + denyApiTakerToggle.setOnAction(null); showOwnOffersInOfferBook.setOnAction(null); resetDontShowAgainButton.setOnAction(null); if (displayStandbyModeFeature) { diff --git a/proto/src/main/proto/pb.proto b/proto/src/main/proto/pb.proto index 0a3a44364d7..a5934665b67 100644 --- a/proto/src/main/proto/pb.proto +++ b/proto/src/main/proto/pb.proto @@ -1617,6 +1617,7 @@ message PreferencesPayload { double bsq_average_trim_threshold = 57; bool hide_non_account_payment_methods = 58; bool show_offers_matching_my_accounts = 59; + bool deny_api_taker = 60; } message AutoConfirmSettings { From c2174607f5547c8b78ba362e96eaf97e77447262 Mon Sep 17 00:00:00 2001 From: chimp1984 Date: Mon, 4 Jan 2021 12:35:29 -0500 Subject: [PATCH 7/9] Add isTakerApiUser field to OfferAvailabilityRequest Add UNCONF_TX_LIMIT_HIT and MAKER_DENIED_API_USER to AvailabilityResult enum Apply handling for api filter features --- core/src/main/java/bisq/core/api/CoreApi.java | 10 + .../java/bisq/core/api/CoreOffersService.java | 15 +- .../java/bisq/core/api/CoreTradesService.java | 2 + .../bisq/core/offer/AvailabilityResult.java | 4 +- .../java/bisq/core/offer/OfferFilter.java | 209 ++++++++++++++++++ .../bisq/core/offer/OpenOfferManager.java | 65 +++--- .../availability/OfferAvailabilityModel.java | 7 +- .../tasks/SendOfferAvailabilityRequest.java | 3 +- .../messages/OfferAvailabilityRequest.java | 11 +- .../java/bisq/core/trade/TradeManager.java | 11 +- .../resources/i18n/displayStrings.properties | 2 + .../main/offer/MutableOfferViewModel.java | 2 +- .../main/offer/offerbook/OfferBookView.java | 176 +++++++-------- .../offer/offerbook/OfferBookViewModel.java | 115 +--------- .../offer/takeoffer/TakeOfferDataModel.java | 4 +- .../offerbook/OfferBookViewModelTest.java | 22 +- proto/src/main/proto/pb.proto | 3 + 17 files changed, 398 insertions(+), 263 deletions(-) create mode 100644 core/src/main/java/bisq/core/offer/OfferFilter.java diff --git a/core/src/main/java/bisq/core/api/CoreApi.java b/core/src/main/java/bisq/core/api/CoreApi.java index 4dffaee9760..caa177664ca 100644 --- a/core/src/main/java/bisq/core/api/CoreApi.java +++ b/core/src/main/java/bisq/core/api/CoreApi.java @@ -112,6 +112,15 @@ public List getOffers(String direction, String currencyCode) { return coreOffersService.getOffers(direction, currencyCode); } + /** + * @param direction The offer direction + * @param currencyCode The offer currency + * @return Returns the offers which can be taken + */ + List getOffersAvailableForTaker(String direction, String currencyCode) { + return coreOffersService.getOffersAvailableForTaker(direction, currencyCode, true); + } + public void createAnPlaceOffer(String currencyCode, String directionAsString, String priceAsString, @@ -202,6 +211,7 @@ public void takeOffer(String offerId, coreTradesService.takeOffer(offer, paymentAccountId, takerFeeCurrencyCode, + true, resultHandler); } diff --git a/core/src/main/java/bisq/core/api/CoreOffersService.java b/core/src/main/java/bisq/core/api/CoreOffersService.java index 0764d33f078..b8a4ef8758e 100644 --- a/core/src/main/java/bisq/core/api/CoreOffersService.java +++ b/core/src/main/java/bisq/core/api/CoreOffersService.java @@ -22,6 +22,7 @@ import bisq.core.offer.CreateOfferService; import bisq.core.offer.Offer; import bisq.core.offer.OfferBookService; +import bisq.core.offer.OfferFilter; import bisq.core.offer.OfferUtil; import bisq.core.offer.OpenOfferManager; import bisq.core.payment.PaymentAccount; @@ -58,20 +59,24 @@ class CoreOffersService { private final OpenOfferManager openOfferManager; private final OfferUtil offerUtil; private final User user; + private final OfferFilter offerFilter; @Inject public CoreOffersService(CreateOfferService createOfferService, OfferBookService offerBookService, OpenOfferManager openOfferManager, OfferUtil offerUtil, - User user) { + User user, + OfferFilter offerFilter) { this.createOfferService = createOfferService; this.offerBookService = offerBookService; this.openOfferManager = openOfferManager; this.offerUtil = offerUtil; this.user = user; + this.offerFilter = offerFilter; } + // TODO should we add a check for offerFilter.canTakeOffer? Offer getOffer(String id) { return offerBookService.getOffers().stream() .filter(o -> o.getId().equals(id)) @@ -79,6 +84,8 @@ Offer getOffer(String id) { new IllegalStateException(format("offer with id '%s' not found", id))); } + // TODO returns all offers also those which cannot be taken. Should we use the filter from + // getOffersAvailableForTaker here and remove the getOffersAvailableForTaker method? List getOffers(String direction, String currencyCode) { List offers = offerBookService.getOffers().stream() .filter(o -> { @@ -99,6 +106,12 @@ List getOffers(String direction, String currencyCode) { return offers; } + List getOffersAvailableForTaker(String direction, String currencyCode, boolean isTakerApiUser) { + return getOffers(direction, currencyCode).stream() + .filter(offer -> offerFilter.canTakeOffer(offer, isTakerApiUser).isValid()) + .collect(Collectors.toList()); + } + // Create and place new offer. void createAndPlaceOffer(String currencyCode, String directionAsString, diff --git a/core/src/main/java/bisq/core/api/CoreTradesService.java b/core/src/main/java/bisq/core/api/CoreTradesService.java index 10d21d6415d..b4d7fcef188 100644 --- a/core/src/main/java/bisq/core/api/CoreTradesService.java +++ b/core/src/main/java/bisq/core/api/CoreTradesService.java @@ -82,6 +82,7 @@ public CoreTradesService(CoreWalletsService coreWalletsService, void takeOffer(Offer offer, String paymentAccountId, String takerFeeCurrencyCode, + boolean isTakerApiUser, Consumer resultHandler) { coreWalletsService.verifyWalletsAreAvailable(); coreWalletsService.verifyEncryptedWalletIsUnlocked(); @@ -108,6 +109,7 @@ void takeOffer(Offer offer, offer, paymentAccountId, useSavingsWallet, + isTakerApiUser, resultHandler::accept, errorMessage -> { log.error(errorMessage); diff --git a/core/src/main/java/bisq/core/offer/AvailabilityResult.java b/core/src/main/java/bisq/core/offer/AvailabilityResult.java index 2d3d749ff24..18e877c8306 100644 --- a/core/src/main/java/bisq/core/offer/AvailabilityResult.java +++ b/core/src/main/java/bisq/core/offer/AvailabilityResult.java @@ -27,5 +27,7 @@ public enum AvailabilityResult { NO_MEDIATORS, USER_IGNORED, MISSING_MANDATORY_CAPABILITY, - NO_REFUND_AGENTS + NO_REFUND_AGENTS, + UNCONF_TX_LIMIT_HIT, + MAKER_DENIED_API_USER } diff --git a/core/src/main/java/bisq/core/offer/OfferFilter.java b/core/src/main/java/bisq/core/offer/OfferFilter.java new file mode 100644 index 00000000000..c22231de5bb --- /dev/null +++ b/core/src/main/java/bisq/core/offer/OfferFilter.java @@ -0,0 +1,209 @@ +/* + * This file is part of Bisq. + * + * Bisq 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. + * + * Bisq 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 Bisq. If not, see . + */ + +package bisq.core.offer; + +import bisq.core.account.witness.AccountAgeWitnessService; +import bisq.core.filter.FilterManager; +import bisq.core.locale.CurrencyUtil; +import bisq.core.payment.PaymentAccount; +import bisq.core.payment.PaymentAccountUtil; +import bisq.core.user.Preferences; +import bisq.core.user.User; + +import bisq.common.app.Version; + +import org.bitcoinj.core.Coin; + +import javax.inject.Inject; +import javax.inject.Singleton; + +import javafx.collections.SetChangeListener; + +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; + +import lombok.Getter; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +@Singleton +public class OfferFilter { + private final User user; + private final Preferences preferences; + private final FilterManager filterManager; + private final AccountAgeWitnessService accountAgeWitnessService; + private final Map insufficientCounterpartyTradeLimitCache = new HashMap<>(); + private final Map myInsufficientTradeLimitCache = new HashMap<>(); + + @Inject + public OfferFilter(User user, + Preferences preferences, + FilterManager filterManager, + AccountAgeWitnessService accountAgeWitnessService) { + this.user = user; + this.preferences = preferences; + this.filterManager = filterManager; + this.accountAgeWitnessService = accountAgeWitnessService; + + if (user != null) { + // If our accounts have changed we reset our myInsufficientTradeLimitCache as it depends on account data + user.getPaymentAccountsAsObservable().addListener((SetChangeListener) c -> + myInsufficientTradeLimitCache.clear()); + } + } + + public enum Result { + VALID(true), + API_DISABLED, + HAS_NO_PAYMENT_ACCOUNT_VALID_FOR_OFFER, + HAS_NOT_SAME_PROTOCOL_VERSION, + IS_IGNORED, + IS_OFFER_BANNED, + IS_CURRENCY_BANNED, + IS_PAYMENT_METHOD_BANNED, + IS_NODE_ADDRESS_BANNED, + REQUIRE_UPDATE_TO_NEW_VERSION, + IS_INSUFFICIENT_COUNTERPARTY_TRADE_LIMIT, + IS_MY_INSUFFICIENT_TRADE_LIMIT; + + @Getter + private final boolean isValid; + + Result(boolean isValid) { + this.isValid = isValid; + } + + Result() { + this(false); + } + } + + public Result canTakeOffer(Offer offer, boolean isTakerApiUser) { + if (isTakerApiUser && filterManager.getFilter() != null && filterManager.getFilter().isDisableApi()) { + return Result.API_DISABLED; + } + if (!isAnyPaymentAccountValidForOffer(offer)) { + return Result.HAS_NO_PAYMENT_ACCOUNT_VALID_FOR_OFFER; + } + if (!hasSameProtocolVersion(offer)) { + return Result.HAS_NOT_SAME_PROTOCOL_VERSION; + } + if (isIgnored(offer)) { + return Result.IS_IGNORED; + } + if (isOfferBanned(offer)) { + return Result.IS_OFFER_BANNED; + } + if (isCurrencyBanned(offer)) { + return Result.IS_CURRENCY_BANNED; + } + if (isPaymentMethodBanned(offer)) { + return Result.IS_PAYMENT_METHOD_BANNED; + } + if (isNodeAddressBanned(offer)) { + return Result.IS_NODE_ADDRESS_BANNED; + } + if (requireUpdateToNewVersion()) { + return Result.REQUIRE_UPDATE_TO_NEW_VERSION; + } + if (isInsufficientCounterpartyTradeLimit(offer)) { + return Result.IS_INSUFFICIENT_COUNTERPARTY_TRADE_LIMIT; + } + if (isMyInsufficientTradeLimit(offer)) { + return Result.IS_MY_INSUFFICIENT_TRADE_LIMIT; + } + + return Result.VALID; + } + + public boolean isAnyPaymentAccountValidForOffer(Offer offer) { + return user.getPaymentAccounts() != null && + PaymentAccountUtil.isAnyTakerPaymentAccountValidForOffer(offer, user.getPaymentAccounts()); + } + + public boolean hasSameProtocolVersion(Offer offer) { + return offer.getProtocolVersion() == Version.TRADE_PROTOCOL_VERSION; + } + + public boolean isIgnored(Offer offer) { + return preferences.getIgnoreTradersList().stream() + .anyMatch(i -> i.equals(offer.getMakerNodeAddress().getFullAddress())); + } + + public boolean isOfferBanned(Offer offer) { + return filterManager.isOfferIdBanned(offer.getId()); + } + + public boolean isCurrencyBanned(Offer offer) { + return filterManager.isCurrencyBanned(offer.getCurrencyCode()); + } + + public boolean isPaymentMethodBanned(Offer offer) { + return filterManager.isPaymentMethodBanned(offer.getPaymentMethod()); + } + + public boolean isNodeAddressBanned(Offer offer) { + return filterManager.isNodeAddressBanned(offer.getMakerNodeAddress()); + } + + public boolean requireUpdateToNewVersion() { + return filterManager.requireUpdateToNewVersionForTrading(); + } + + // This call is a bit expensive so we cache results + public boolean isInsufficientCounterpartyTradeLimit(Offer offer) { + String offerId = offer.getId(); + if (insufficientCounterpartyTradeLimitCache.containsKey(offerId)) { + return insufficientCounterpartyTradeLimitCache.get(offerId); + } + + boolean result = CurrencyUtil.isFiatCurrency(offer.getCurrencyCode()) && + !accountAgeWitnessService.verifyPeersTradeAmount(offer, offer.getAmount(), + errorMessage -> { + }); + insufficientCounterpartyTradeLimitCache.put(offerId, result); + return result; + } + + // This call is a bit expensive so we cache results + public boolean isMyInsufficientTradeLimit(Offer offer) { + String offerId = offer.getId(); + if (myInsufficientTradeLimitCache.containsKey(offerId)) { + return myInsufficientTradeLimitCache.get(offerId); + } + + Optional accountOptional = PaymentAccountUtil.getMostMaturePaymentAccountForOffer(offer, + user.getPaymentAccounts(), + accountAgeWitnessService); + long myTradeLimit = accountOptional + .map(paymentAccount -> accountAgeWitnessService.getMyTradeLimit(paymentAccount, + offer.getCurrencyCode(), offer.getMirroredDirection())) + .orElse(0L); + long offerMinAmount = offer.getMinAmount().value; + log.debug("isInsufficientTradeLimit accountOptional={}, myTradeLimit={}, offerMinAmount={}, ", + accountOptional.isPresent() ? accountOptional.get().getAccountName() : "null", + Coin.valueOf(myTradeLimit).toFriendlyString(), + Coin.valueOf(offerMinAmount).toFriendlyString()); + boolean result = CurrencyUtil.isFiatCurrency(offer.getCurrencyCode()) && + accountOptional.isPresent() && + myTradeLimit < offerMinAmount; + myInsufficientTradeLimitCache.put(offerId, result); + return result; + } +} diff --git a/core/src/main/java/bisq/core/offer/OpenOfferManager.java b/core/src/main/java/bisq/core/offer/OpenOfferManager.java index 3e16c784024..3f07eaa1f45 100644 --- a/core/src/main/java/bisq/core/offer/OpenOfferManager.java +++ b/core/src/main/java/bisq/core/offer/OpenOfferManager.java @@ -634,47 +634,50 @@ private void handleOfferAvailabilityRequest(OfferAvailabilityRequest request, No NodeAddress refundAgentNodeAddress = null; if (openOfferOptional.isPresent()) { OpenOffer openOffer = openOfferOptional.get(); - if (openOffer.getState() == OpenOffer.State.AVAILABLE) { - Offer offer = openOffer.getOffer(); - if (preferences.getIgnoreTradersList().stream().noneMatch(fullAddress -> fullAddress.equals(peer.getFullAddress()))) { - availabilityResult = AvailabilityResult.AVAILABLE; - - mediatorNodeAddress = DisputeAgentSelection.getLeastUsedMediator(tradeStatisticsManager, mediatorManager).getNodeAddress(); - openOffer.setMediatorNodeAddress(mediatorNodeAddress); - - refundAgentNodeAddress = DisputeAgentSelection.getLeastUsedRefundAgent(tradeStatisticsManager, refundAgentManager).getNodeAddress(); - openOffer.setRefundAgentNodeAddress(refundAgentNodeAddress); - - try { - // Check also tradePrice to avoid failures after taker fee is paid caused by a too big difference - // in trade price between the peers. Also here poor connectivity might cause market price API connection - // losses and therefore an outdated market price. - offer.checkTradePriceTolerance(request.getTakersTradePrice()); - } catch (TradePriceOutOfToleranceException e) { - log.warn("Trade price check failed because takers price is outside out tolerance."); - availabilityResult = AvailabilityResult.PRICE_OUT_OF_TOLERANCE; - } catch (MarketPriceNotAvailableException e) { - log.warn(e.getMessage()); - availabilityResult = AvailabilityResult.MARKET_PRICE_NOT_AVAILABLE; - } catch (Throwable e) { - log.warn("Trade price check failed. " + e.getMessage()); - availabilityResult = AvailabilityResult.UNKNOWN_FAILURE; + if (!apiUserDeniedByOffer(request)) { + if (openOffer.getState() == OpenOffer.State.AVAILABLE) { + Offer offer = openOffer.getOffer(); + if (preferences.getIgnoreTradersList().stream().noneMatch(fullAddress -> fullAddress.equals(peer.getFullAddress()))) { + mediatorNodeAddress = DisputeAgentSelection.getLeastUsedMediator(tradeStatisticsManager, mediatorManager).getNodeAddress(); + openOffer.setMediatorNodeAddress(mediatorNodeAddress); + + refundAgentNodeAddress = DisputeAgentSelection.getLeastUsedRefundAgent(tradeStatisticsManager, refundAgentManager).getNodeAddress(); + openOffer.setRefundAgentNodeAddress(refundAgentNodeAddress); + + try { + // Check also tradePrice to avoid failures after taker fee is paid caused by a too big difference + // in trade price between the peers. Also here poor connectivity might cause market price API connection + // losses and therefore an outdated market price. + offer.checkTradePriceTolerance(request.getTakersTradePrice()); + availabilityResult = AvailabilityResult.AVAILABLE; + } catch (TradePriceOutOfToleranceException e) { + log.warn("Trade price check failed because takers price is outside out tolerance."); + availabilityResult = AvailabilityResult.PRICE_OUT_OF_TOLERANCE; + } catch (MarketPriceNotAvailableException e) { + log.warn(e.getMessage()); + availabilityResult = AvailabilityResult.MARKET_PRICE_NOT_AVAILABLE; + } catch (Throwable e) { + log.warn("Trade price check failed. " + e.getMessage()); + availabilityResult = AvailabilityResult.UNKNOWN_FAILURE; + } + } else { + availabilityResult = AvailabilityResult.USER_IGNORED; } } else { - availabilityResult = AvailabilityResult.USER_IGNORED; + availabilityResult = AvailabilityResult.OFFER_TAKEN; } } else { - availabilityResult = AvailabilityResult.OFFER_TAKEN; + availabilityResult = AvailabilityResult.MAKER_DENIED_API_USER; } } else { - log.warn("handleOfferAvailabilityRequest: openOffer not found. That should never happen."); + log.warn("handleOfferAvailabilityRequest: openOffer not found."); availabilityResult = AvailabilityResult.OFFER_TAKEN; } if (btcWalletService.isUnconfirmedTransactionsLimitHit() || bsqWalletService.isUnconfirmedTransactionsLimitHit()) { errorMessage = Res.get("shared.unconfirmedTransactionsLimitReached"); log.warn(errorMessage); - availabilityResult = AvailabilityResult.UNKNOWN_FAILURE; + availabilityResult = AvailabilityResult.UNCONF_TX_LIMIT_HIT; } OfferAvailabilityResponse offerAvailabilityResponse = new OfferAvailabilityResponse(request.offerId, @@ -716,6 +719,10 @@ public void onFault(String errorMessage) { } } + private boolean apiUserDeniedByOffer(OfferAvailabilityRequest request) { + return preferences.isDenyApiTaker() && request.isTakerApiUser(); + } + private void sendAckMessage(OfferAvailabilityRequest message, NodeAddress sender, boolean result, diff --git a/core/src/main/java/bisq/core/offer/availability/OfferAvailabilityModel.java b/core/src/main/java/bisq/core/offer/availability/OfferAvailabilityModel.java index 8d183d40857..c1559cec8d3 100644 --- a/core/src/main/java/bisq/core/offer/availability/OfferAvailabilityModel.java +++ b/core/src/main/java/bisq/core/offer/availability/OfferAvailabilityModel.java @@ -66,19 +66,24 @@ public class OfferAvailabilityModel implements Model { @Getter private NodeAddress selectedRefundAgent; + // Added in v1.5.5 + @Getter + private final boolean isTakerApiUser; public OfferAvailabilityModel(Offer offer, PubKeyRing pubKeyRing, P2PService p2PService, User user, MediatorManager mediatorManager, - TradeStatisticsManager tradeStatisticsManager) { + TradeStatisticsManager tradeStatisticsManager, + boolean isTakerApiUser) { this.offer = offer; this.pubKeyRing = pubKeyRing; this.p2PService = p2PService; this.user = user; this.mediatorManager = mediatorManager; this.tradeStatisticsManager = tradeStatisticsManager; + this.isTakerApiUser = isTakerApiUser; } public NodeAddress getPeerNodeAddress() { diff --git a/core/src/main/java/bisq/core/offer/availability/tasks/SendOfferAvailabilityRequest.java b/core/src/main/java/bisq/core/offer/availability/tasks/SendOfferAvailabilityRequest.java index 3ee88536c64..0dbc8e69ea4 100644 --- a/core/src/main/java/bisq/core/offer/availability/tasks/SendOfferAvailabilityRequest.java +++ b/core/src/main/java/bisq/core/offer/availability/tasks/SendOfferAvailabilityRequest.java @@ -39,7 +39,8 @@ protected void run() { try { runInterceptHook(); - OfferAvailabilityRequest message = new OfferAvailabilityRequest(model.getOffer().getId(), model.getPubKeyRing(), model.getTakersTradePrice()); + OfferAvailabilityRequest message = new OfferAvailabilityRequest(model.getOffer().getId(), + model.getPubKeyRing(), model.getTakersTradePrice(), model.isTakerApiUser()); log.info("Send {} with offerId {} and uid {} to peer {}", message.getClass().getSimpleName(), message.getOfferId(), message.getUid(), model.getPeerNodeAddress()); diff --git a/core/src/main/java/bisq/core/offer/messages/OfferAvailabilityRequest.java b/core/src/main/java/bisq/core/offer/messages/OfferAvailabilityRequest.java index a3cbd1c9d06..6d9d14eaf61 100644 --- a/core/src/main/java/bisq/core/offer/messages/OfferAvailabilityRequest.java +++ b/core/src/main/java/bisq/core/offer/messages/OfferAvailabilityRequest.java @@ -42,13 +42,16 @@ public final class OfferAvailabilityRequest extends OfferMessage implements Supp private final long takersTradePrice; @Nullable private final Capabilities supportedCapabilities; + private final boolean isTakerApiUser; public OfferAvailabilityRequest(String offerId, PubKeyRing pubKeyRing, - long takersTradePrice) { + long takersTradePrice, + boolean isTakerApiUser) { this(offerId, pubKeyRing, takersTradePrice, + isTakerApiUser, Capabilities.app, Version.getP2PMessageVersion(), UUID.randomUUID().toString()); @@ -62,12 +65,14 @@ public OfferAvailabilityRequest(String offerId, private OfferAvailabilityRequest(String offerId, PubKeyRing pubKeyRing, long takersTradePrice, + boolean isTakerApiUser, @Nullable Capabilities supportedCapabilities, int messageVersion, @Nullable String uid) { super(messageVersion, offerId, uid); this.pubKeyRing = pubKeyRing; this.takersTradePrice = takersTradePrice; + this.isTakerApiUser = isTakerApiUser; this.supportedCapabilities = supportedCapabilities; } @@ -76,7 +81,8 @@ public protobuf.NetworkEnvelope toProtoNetworkEnvelope() { final protobuf.OfferAvailabilityRequest.Builder builder = protobuf.OfferAvailabilityRequest.newBuilder() .setOfferId(offerId) .setPubKeyRing(pubKeyRing.toProtoMessage()) - .setTakersTradePrice(takersTradePrice); + .setTakersTradePrice(takersTradePrice) + .setIsTakerApiUser(isTakerApiUser); Optional.ofNullable(supportedCapabilities).ifPresent(e -> builder.addAllSupportedCapabilities(Capabilities.toIntList(supportedCapabilities))); Optional.ofNullable(uid).ifPresent(e -> builder.setUid(uid)); @@ -90,6 +96,7 @@ public static OfferAvailabilityRequest fromProto(protobuf.OfferAvailabilityReque return new OfferAvailabilityRequest(proto.getOfferId(), PubKeyRing.fromProto(proto.getPubKeyRing()), proto.getTakersTradePrice(), + proto.getIsTakerApiUser(), Capabilities.fromIntList(proto.getSupportedCapabilitiesList()), messageVersion, proto.getUid().isEmpty() ? null : proto.getUid()); diff --git a/core/src/main/java/bisq/core/trade/TradeManager.java b/core/src/main/java/bisq/core/trade/TradeManager.java index dc87d4a98cc..6707fde8ea7 100644 --- a/core/src/main/java/bisq/core/trade/TradeManager.java +++ b/core/src/main/java/bisq/core/trade/TradeManager.java @@ -373,6 +373,7 @@ public void requestPersistence() { /////////////////////////////////////////////////////////////////////////////////////////// public void checkOfferAvailability(Offer offer, + boolean isTakerApiUser, ResultHandler resultHandler, ErrorMessageHandler errorMessageHandler) { if (btcWalletService.isUnconfirmedTransactionsLimitHit() || @@ -383,7 +384,7 @@ public void checkOfferAvailability(Offer offer, return; } - offer.checkOfferAvailability(getOfferAvailabilityModel(offer), resultHandler, errorMessageHandler); + offer.checkOfferAvailability(getOfferAvailabilityModel(offer, isTakerApiUser), resultHandler, errorMessageHandler); } // First we check if offer is still available then we create the trade with the protocol @@ -396,12 +397,13 @@ public void onTakeOffer(Coin amount, Offer offer, String paymentAccountId, boolean useSavingsWallet, + boolean isTakerApiUser, TradeResultHandler tradeResultHandler, ErrorMessageHandler errorMessageHandler) { checkArgument(!wasOfferAlreadyUsedInTrade(offer.getId())); - OfferAvailabilityModel model = getOfferAvailabilityModel(offer); + OfferAvailabilityModel model = getOfferAvailabilityModel(offer, isTakerApiUser); offer.checkOfferAvailability(model, () -> { if (offer.getState() == Offer.State.AVAILABLE) { @@ -464,14 +466,15 @@ private ProcessModel getNewProcessModel(Offer offer) { processModelServiceProvider.getKeyRing().getPubKeyRing()); } - private OfferAvailabilityModel getOfferAvailabilityModel(Offer offer) { + private OfferAvailabilityModel getOfferAvailabilityModel(Offer offer, boolean isTakerApiUser) { return new OfferAvailabilityModel( offer, keyRing.getPubKeyRing(), p2PService, user, mediatorManager, - tradeStatisticsManager); + tradeStatisticsManager, + isTakerApiUser); } diff --git a/core/src/main/resources/i18n/displayStrings.properties b/core/src/main/resources/i18n/displayStrings.properties index 4af5a97364c..fa728340308 100644 --- a/core/src/main/resources/i18n/displayStrings.properties +++ b/core/src/main/resources/i18n/displayStrings.properties @@ -1223,6 +1223,7 @@ setting.preferences.useAnimations=Use animations setting.preferences.useDarkMode=Use dark mode setting.preferences.sortWithNumOffers=Sort market lists with no. of offers/trades setting.preferences.onlyShowPaymentMethodsFromAccount=Hide non-supported payment methods +setting.preferences.denyApiTaker=Deny takers using the API setting.preferences.resetAllFlags=Reset all \"Don't show again\" flags settings.preferences.languageChange=To apply the language change to all screens requires a restart. settings.preferences.supportLanguageWarning=In case of a dispute, please note that mediation is handled in {0} and arbitration in {1}. @@ -2620,6 +2621,7 @@ filterWindow.disableTradeBelowVersion=Min. version required for trading filterWindow.add=Add filter filterWindow.remove=Remove filter filterWindow.btcFeeReceiverAddresses=BTC fee receiver addresses +filterWindow.disableApi=Disable API offerDetailsWindow.minBtcAmount=Min. BTC amount offerDetailsWindow.min=(min. {0}) diff --git a/desktop/src/main/java/bisq/desktop/main/offer/MutableOfferViewModel.java b/desktop/src/main/java/bisq/desktop/main/offer/MutableOfferViewModel.java index bdbb89cb331..8d435ede855 100644 --- a/desktop/src/main/java/bisq/desktop/main/offer/MutableOfferViewModel.java +++ b/desktop/src/main/java/bisq/desktop/main/offer/MutableOfferViewModel.java @@ -236,7 +236,7 @@ public void activate() { if (DevEnv.isDevMode()) { UserThread.runAfter(() -> { amount.set("0.001"); - price.set("70000"); + price.set("210000"); minAmount.set(amount.get()); onFocusOutPriceAsPercentageTextField(true, false); applyMakerFee(); diff --git a/desktop/src/main/java/bisq/desktop/main/offer/offerbook/OfferBookView.java b/desktop/src/main/java/bisq/desktop/main/offer/offerbook/OfferBookView.java index 6c3fa3d6c24..caa01deea4a 100644 --- a/desktop/src/main/java/bisq/desktop/main/offer/offerbook/OfferBookView.java +++ b/desktop/src/main/java/bisq/desktop/main/offer/offerbook/OfferBookView.java @@ -52,6 +52,7 @@ import bisq.core.locale.TradeCurrency; import bisq.core.monetary.Price; import bisq.core.offer.Offer; +import bisq.core.offer.OfferFilter; import bisq.core.offer.OfferPayload; import bisq.core.offer.OfferRestrictions; import bisq.core.payment.PaymentAccount; @@ -62,6 +63,7 @@ import bisq.network.p2p.NodeAddress; +import bisq.common.app.DevEnv; import bisq.common.config.Config; import bisq.common.util.Tuple3; @@ -617,51 +619,61 @@ private void onCreateOffer() { } } - private void onShowInfo(Offer offer, - boolean isPaymentAccountValidForOffer, - boolean isInsufficientCounterpartyTradeLimit, - boolean hasSameProtocolVersion, - boolean isIgnored, - boolean isOfferBanned, - boolean isCurrencyBanned, - boolean isPaymentMethodBanned, - boolean isNodeAddressBanned, - boolean requireUpdateToNewVersion, - boolean isInsufficientTradeLimit) { - if (!isPaymentAccountValidForOffer) { - openPopupForMissingAccountSetup(Res.get("offerbook.warning.noMatchingAccount.headline"), - Res.get("offerbook.warning.noMatchingAccount.msg"), - FiatAccountsView.class, - "navigation.account"); - } else if (isInsufficientCounterpartyTradeLimit) { - new Popup().warning(Res.get("offerbook.warning.counterpartyTradeRestrictions")).show(); - } else if (!hasSameProtocolVersion) { - new Popup().warning(Res.get("offerbook.warning.wrongTradeProtocol")).show(); - } else if (isIgnored) { - new Popup().warning(Res.get("offerbook.warning.userIgnored")).show(); - } else if (isOfferBanned) { - new Popup().warning(Res.get("offerbook.warning.offerBlocked")).show(); - } else if (isCurrencyBanned) { - new Popup().warning(Res.get("offerbook.warning.currencyBanned")).show(); - } else if (isPaymentMethodBanned) { - new Popup().warning(Res.get("offerbook.warning.paymentMethodBanned")).show(); - } else if (isNodeAddressBanned) { - new Popup().warning(Res.get("offerbook.warning.nodeBlocked")).show(); - } else if (requireUpdateToNewVersion) { - new Popup().warning(Res.get("offerbook.warning.requireUpdateToNewVersion")).show(); - } else if (isInsufficientTradeLimit) { - final Optional account = model.getMostMaturePaymentAccountForOffer(offer); - if (account.isPresent()) { - final long tradeLimit = model.accountAgeWitnessService.getMyTradeLimit(account.get(), - offer.getCurrencyCode(), offer.getMirroredDirection()); - new Popup() - .warning(Res.get("popup.warning.tradeLimitDueAccountAgeRestriction.buyer", - formatter.formatCoinWithCode(Coin.valueOf(tradeLimit)), - Res.get("offerbook.warning.newVersionAnnouncement"))) - .show(); - } else { - log.warn("We don't found a payment account but got called the isInsufficientTradeLimit case. That must not happen."); - } + private void onShowInfo(Offer offer, OfferFilter.Result result) { + switch (result) { + case VALID: + break; + case API_DISABLED: + DevEnv.logErrorAndThrowIfDevMode("We are in desktop and in the taker position " + + "viewing offers, so it cannot be that we got that result as we are not an API user."); + break; + case HAS_NO_PAYMENT_ACCOUNT_VALID_FOR_OFFER: + openPopupForMissingAccountSetup(Res.get("offerbook.warning.noMatchingAccount.headline"), + Res.get("offerbook.warning.noMatchingAccount.msg"), + FiatAccountsView.class, + "navigation.account"); + break; + case HAS_NOT_SAME_PROTOCOL_VERSION: + new Popup().warning(Res.get("offerbook.warning.wrongTradeProtocol")).show(); + break; + case IS_IGNORED: + new Popup().warning(Res.get("offerbook.warning.userIgnored")).show(); + break; + case IS_OFFER_BANNED: + new Popup().warning(Res.get("offerbook.warning.offerBlocked")).show(); + break; + case IS_CURRENCY_BANNED: + new Popup().warning(Res.get("offerbook.warning.currencyBanned")).show(); + break; + case IS_PAYMENT_METHOD_BANNED: + new Popup().warning(Res.get("offerbook.warning.paymentMethodBanned")).show(); + break; + case IS_NODE_ADDRESS_BANNED: + new Popup().warning(Res.get("offerbook.warning.nodeBlocked")).show(); + break; + case REQUIRE_UPDATE_TO_NEW_VERSION: + new Popup().warning(Res.get("offerbook.warning.requireUpdateToNewVersion")).show(); + break; + case IS_INSUFFICIENT_COUNTERPARTY_TRADE_LIMIT: + new Popup().warning(Res.get("offerbook.warning.counterpartyTradeRestrictions")).show(); + break; + case IS_MY_INSUFFICIENT_TRADE_LIMIT: + Optional account = model.getMostMaturePaymentAccountForOffer(offer); + if (account.isPresent()) { + long tradeLimit = model.accountAgeWitnessService.getMyTradeLimit(account.get(), + offer.getCurrencyCode(), offer.getMirroredDirection()); + new Popup() + .warning(Res.get("popup.warning.tradeLimitDueAccountAgeRestriction.buyer", + formatter.formatCoinWithCode(Coin.valueOf(tradeLimit)), + Res.get("offerbook.warning.newVersionAnnouncement"))) + .show(); + } else { + DevEnv.logErrorAndThrowIfDevMode("We don't found a payment account but got called the " + + "isInsufficientTradeLimit case."); + } + break; + default: + break; } } @@ -1015,11 +1027,7 @@ public TableCell call(TableColumn() { final ImageView iconView = new ImageView(); final AutoTooltipButton button = new AutoTooltipButton(); - boolean isTradable, isPaymentAccountValidForOffer, - isInsufficientCounterpartyTradeLimit, - hasSameProtocolVersion, isIgnored, isOfferBanned, isCurrencyBanned, - isPaymentMethodBanned, isNodeAddressBanned, isMyInsufficientTradeLimit, - requireUpdateToNewVersion; + OfferFilter.Result canTakeOfferResult = null; { button.setGraphic(iconView); @@ -1034,37 +1042,14 @@ public void updateItem(final OfferBookListItem item, boolean empty) { TableRow tableRow = getTableRow(); if (item != null && !empty) { - final Offer offer = item.getOffer(); + Offer offer = item.getOffer(); boolean myOffer = model.isMyOffer(offer); + if (tableRow != null) { - // this code is duplicated in model.getOffersMatchingMyAccountsPredicate but as - // we want to pass the results for displaying relevant info in popups we - // cannot simply replace it with the predicate. If there are any changes we - // need to maintain both. - isPaymentAccountValidForOffer = model.isAnyPaymentAccountValidForOffer(offer); - isInsufficientCounterpartyTradeLimit = model.isInsufficientCounterpartyTradeLimit(offer); - hasSameProtocolVersion = model.hasSameProtocolVersion(offer); - isIgnored = model.isIgnored(offer); - isOfferBanned = model.isOfferBanned(offer); - isCurrencyBanned = model.isCurrencyBanned(offer); - isPaymentMethodBanned = model.isPaymentMethodBanned(offer); - isNodeAddressBanned = model.isNodeAddressBanned(offer); - requireUpdateToNewVersion = model.requireUpdateToNewVersion(); - isMyInsufficientTradeLimit = model.isMyInsufficientTradeLimit(offer); - isTradable = isPaymentAccountValidForOffer && - !isInsufficientCounterpartyTradeLimit && - hasSameProtocolVersion && - !isIgnored && - !isOfferBanned && - !isCurrencyBanned && - !isPaymentMethodBanned && - !isNodeAddressBanned && - !requireUpdateToNewVersion && - !isMyInsufficientTradeLimit; - - tableRow.setOpacity(isTradable || myOffer ? 1 : 0.4); - - if (isTradable) { + canTakeOfferResult = model.offerFilter.canTakeOffer(offer, false); + tableRow.setOpacity(canTakeOfferResult.isValid() || myOffer ? 1 : 0.4); + + if (canTakeOfferResult.isValid()) { // set first row button as default button.setDefaultButton(getIndex() == 0); tableRow.setOnMousePressed(null); @@ -1073,17 +1058,7 @@ public void updateItem(final OfferBookListItem item, boolean empty) { tableRow.setOnMousePressed(e -> { // ugly hack to get the icon clickable when deactivated if (!(e.getTarget() instanceof ImageView || e.getTarget() instanceof Canvas)) - onShowInfo(offer, - isPaymentAccountValidForOffer, - isInsufficientCounterpartyTradeLimit, - hasSameProtocolVersion, - isIgnored, - isOfferBanned, - isCurrencyBanned, - isPaymentMethodBanned, - isNodeAddressBanned, - requireUpdateToNewVersion, - isMyInsufficientTradeLimit); + onShowInfo(offer, canTakeOfferResult); }); } } @@ -1113,18 +1088,15 @@ public void updateItem(final OfferBookListItem item, boolean empty) { button.setOnAction(e -> onTakeOffer(offer)); } - if (!myOffer && !isTradable) - button.setOnAction(e -> onShowInfo(offer, - isPaymentAccountValidForOffer, - isInsufficientCounterpartyTradeLimit, - hasSameProtocolVersion, - isIgnored, - isOfferBanned, - isCurrencyBanned, - isPaymentMethodBanned, - isNodeAddressBanned, - requireUpdateToNewVersion, - isMyInsufficientTradeLimit)); + if (!myOffer) { + if (canTakeOfferResult == null) { + canTakeOfferResult = model.offerFilter.canTakeOffer(offer, false); + } + + if (!canTakeOfferResult.isValid()) { + button.setOnAction(e -> onShowInfo(offer, canTakeOfferResult)); + } + } button.updateText(title); setPadding(new Insets(0, 15, 0, 0)); diff --git a/desktop/src/main/java/bisq/desktop/main/offer/offerbook/OfferBookViewModel.java b/desktop/src/main/java/bisq/desktop/main/offer/offerbook/OfferBookViewModel.java index 2a69f863fca..6e0ddd0fa3d 100644 --- a/desktop/src/main/java/bisq/desktop/main/offer/offerbook/OfferBookViewModel.java +++ b/desktop/src/main/java/bisq/desktop/main/offer/offerbook/OfferBookViewModel.java @@ -28,7 +28,6 @@ import bisq.core.account.witness.AccountAgeWitnessService; import bisq.core.btc.setup.WalletsSetup; -import bisq.core.filter.FilterManager; import bisq.core.locale.BankUtil; import bisq.core.locale.CountryUtil; import bisq.core.locale.CryptoCurrency; @@ -39,6 +38,7 @@ import bisq.core.monetary.Price; import bisq.core.monetary.Volume; import bisq.core.offer.Offer; +import bisq.core.offer.OfferFilter; import bisq.core.offer.OfferPayload; import bisq.core.offer.OpenOfferManager; import bisq.core.payment.PaymentAccount; @@ -56,7 +56,6 @@ import bisq.network.p2p.NodeAddress; import bisq.network.p2p.P2PService; -import bisq.common.app.Version; import bisq.common.handlers.ErrorMessageHandler; import bisq.common.handlers.ResultHandler; @@ -78,14 +77,12 @@ import javafx.collections.FXCollections; import javafx.collections.ListChangeListener; import javafx.collections.ObservableList; -import javafx.collections.SetChangeListener; import javafx.collections.transformation.FilteredList; import javafx.collections.transformation.SortedList; import java.text.DecimalFormat; import java.util.Comparator; -import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Optional; @@ -105,10 +102,10 @@ class OfferBookViewModel extends ActivatableViewModel { private final P2PService p2PService; final PriceFeedService priceFeedService; private final ClosedTradableManager closedTradableManager; - private final FilterManager filterManager; final AccountAgeWitnessService accountAgeWitnessService; private final Navigation navigation; private final PriceUtil priceUtil; + final OfferFilter offerFilter; private final CoinFormatter btcFormatter; private final BsqFormatter bsqFormatter; @@ -136,8 +133,6 @@ class OfferBookViewModel extends ActivatableViewModel { final IntegerProperty maxPlacesForMarketPriceMargin = new SimpleIntegerProperty(); boolean showAllPaymentMethods = true; boolean useOffersMatchingMyAccountsFilter; - private final Map myInsufficientTradeLimitCache = new HashMap<>(); - private final Map insufficientCounterpartyTradeLimitCache = new HashMap<>(); /////////////////////////////////////////////////////////////////////////////////////////// @@ -153,10 +148,10 @@ public OfferBookViewModel(User user, P2PService p2PService, PriceFeedService priceFeedService, ClosedTradableManager closedTradableManager, - FilterManager filterManager, AccountAgeWitnessService accountAgeWitnessService, Navigation navigation, PriceUtil priceUtil, + OfferFilter offerFilter, @Named(FormattingUtils.BTC_FORMATTER_KEY) CoinFormatter btcFormatter, BsqFormatter bsqFormatter) { super(); @@ -169,10 +164,10 @@ public OfferBookViewModel(User user, this.p2PService = p2PService; this.priceFeedService = priceFeedService; this.closedTradableManager = closedTradableManager; - this.filterManager = filterManager; this.accountAgeWitnessService = accountAgeWitnessService; this.navigation = navigation; this.priceUtil = priceUtil; + this.offerFilter = offerFilter; this.btcFormatter = btcFormatter; this.bsqFormatter = bsqFormatter; @@ -213,12 +208,6 @@ public OfferBookViewModel(User user, highestMarketPriceMarginOffer.ifPresent(offerBookListItem -> maxPlacesForMarketPriceMargin.set(formatMarketPriceMargin(offerBookListItem.getOffer(), false).length())); }; - - // If our accounts have changed we reset our myInsufficientTradeLimitCache as it depends on account data - if (user != null) { - user.getPaymentAccountsAsObservable().addListener((SetChangeListener) c -> - myInsufficientTradeLimitCache.clear()); - } } @Override @@ -568,11 +557,6 @@ private void fillAllTradeCurrencies() { // Checks /////////////////////////////////////////////////////////////////////////////////////////// - boolean isAnyPaymentAccountValidForOffer(Offer offer) { - return user.getPaymentAccounts() != null && - PaymentAccountUtil.isAnyTakerPaymentAccountValidForOffer(offer, user.getPaymentAccounts()); - } - boolean hasPaymentAccountForCurrency() { return (showAllTradeCurrenciesProperty.get() && user.getPaymentAccounts() != null && @@ -615,98 +599,11 @@ private Predicate getOffersMatchingMyAccountsPredicate() { // This code duplicates code in the view at the button column. We need there the different results for // display in popups so we cannot replace that with the predicate. Any change need to be applied in both // places. - return offerBookListItem -> { - Offer offer = offerBookListItem.getOffer(); - boolean isPaymentAccountValidForOffer = isAnyPaymentAccountValidForOffer(offer); - boolean isInsufficientCounterpartyTradeLimit = isInsufficientCounterpartyTradeLimit(offer); - boolean hasSameProtocolVersion = hasSameProtocolVersion(offer); - boolean isIgnored = isIgnored(offer); - boolean isOfferBanned = isOfferBanned(offer); - boolean isCurrencyBanned = isCurrencyBanned(offer); - boolean isPaymentMethodBanned = isPaymentMethodBanned(offer); - boolean isNodeAddressBanned = isNodeAddressBanned(offer); - boolean requireUpdateToNewVersion = requireUpdateToNewVersion(); - boolean isMyInsufficientTradeLimit = isMyInsufficientTradeLimit(offer); - boolean isTradable = isPaymentAccountValidForOffer && - !isInsufficientCounterpartyTradeLimit && - hasSameProtocolVersion && - !isIgnored && - !isOfferBanned && - !isCurrencyBanned && - !isPaymentMethodBanned && - !isNodeAddressBanned && - !requireUpdateToNewVersion && - !isMyInsufficientTradeLimit; - return isTradable; - }; - } - - boolean isIgnored(Offer offer) { - return preferences.getIgnoreTradersList().stream() - .anyMatch(i -> i.equals(offer.getMakerNodeAddress().getFullAddress())); + return offerBookListItem -> offerFilter.canTakeOffer(offerBookListItem.getOffer(), false).isValid(); } boolean isOfferBanned(Offer offer) { - return filterManager.isOfferIdBanned(offer.getId()); - } - - boolean isCurrencyBanned(Offer offer) { - return filterManager.isCurrencyBanned(offer.getCurrencyCode()); - } - - boolean isPaymentMethodBanned(Offer offer) { - return filterManager.isPaymentMethodBanned(offer.getPaymentMethod()); - } - - boolean isNodeAddressBanned(Offer offer) { - return filterManager.isNodeAddressBanned(offer.getMakerNodeAddress()); - } - - boolean requireUpdateToNewVersion() { - return filterManager.requireUpdateToNewVersionForTrading(); - } - - // This call is a bit expensive so we cache results - boolean isInsufficientCounterpartyTradeLimit(Offer offer) { - String offerId = offer.getId(); - if (insufficientCounterpartyTradeLimitCache.containsKey(offerId)) { - return insufficientCounterpartyTradeLimitCache.get(offerId); - } - - boolean result = CurrencyUtil.isFiatCurrency(offer.getCurrencyCode()) && - !accountAgeWitnessService.verifyPeersTradeAmount(offer, offer.getAmount(), - errorMessage -> { - }); - insufficientCounterpartyTradeLimitCache.put(offerId, result); - return result; - } - - // This call is a bit expensive so we cache results - boolean isMyInsufficientTradeLimit(Offer offer) { - String offerId = offer.getId(); - if (myInsufficientTradeLimitCache.containsKey(offerId)) { - return myInsufficientTradeLimitCache.get(offerId); - } - - Optional accountOptional = getMostMaturePaymentAccountForOffer(offer); - long myTradeLimit = accountOptional - .map(paymentAccount -> accountAgeWitnessService.getMyTradeLimit(paymentAccount, - offer.getCurrencyCode(), offer.getMirroredDirection())) - .orElse(0L); - long offerMinAmount = offer.getMinAmount().value; - log.debug("isInsufficientTradeLimit accountOptional={}, myTradeLimit={}, offerMinAmount={}, ", - accountOptional.isPresent() ? accountOptional.get().getAccountName() : "null", - Coin.valueOf(myTradeLimit).toFriendlyString(), - Coin.valueOf(offerMinAmount).toFriendlyString()); - boolean result = CurrencyUtil.isFiatCurrency(offer.getCurrencyCode()) && - accountOptional.isPresent() && - myTradeLimit < offerMinAmount; - myInsufficientTradeLimitCache.put(offerId, result); - return result; - } - - boolean hasSameProtocolVersion(Offer offer) { - return offer.getProtocolVersion() == Version.TRADE_PROTOCOL_VERSION; + return offerFilter.isOfferBanned(offer); } private boolean isShowAllEntry(String id) { diff --git a/desktop/src/main/java/bisq/desktop/main/offer/takeoffer/TakeOfferDataModel.java b/desktop/src/main/java/bisq/desktop/main/offer/takeoffer/TakeOfferDataModel.java index dc90ce04559..9c3eacd3308 100644 --- a/desktop/src/main/java/bisq/desktop/main/offer/takeoffer/TakeOfferDataModel.java +++ b/desktop/src/main/java/bisq/desktop/main/offer/takeoffer/TakeOfferDataModel.java @@ -171,6 +171,7 @@ protected void activate() { if (canTakeOffer()) { tradeManager.checkOfferAvailability(offer, + false, () -> { }, errorMessage -> new Popup().warning(errorMessage).show()); @@ -319,7 +320,8 @@ void onTakeOffer(TradeResultHandler tradeResultHandler) { offer, paymentAccount.getId(), useSavingsWallet, - tradeResultHandler::handleResult, + false, + tradeResultHandler, errorMessage -> { log.warn(errorMessage); new Popup().warning(errorMessage).show(); diff --git a/desktop/src/test/java/bisq/desktop/main/offer/offerbook/OfferBookViewModelTest.java b/desktop/src/test/java/bisq/desktop/main/offer/offerbook/OfferBookViewModelTest.java index 39044f8d526..dc48c8c5459 100644 --- a/desktop/src/test/java/bisq/desktop/main/offer/offerbook/OfferBookViewModelTest.java +++ b/desktop/src/test/java/bisq/desktop/main/offer/offerbook/OfferBookViewModelTest.java @@ -239,7 +239,7 @@ public void testMaxCharactersForAmountWithNoOffes() { when(offerBook.getOfferBookListItems()).thenReturn(offerBookListItems); final OfferBookViewModel model = new OfferBookViewModel(null, null, offerBook, empty, null, null, null, - null, null, null, null, getPriceUtil(), coinFormatter, new BsqFormatter()); + null, null, null, getPriceUtil(), null, coinFormatter, new BsqFormatter()); assertEquals(0, model.maxPlacesForAmount.intValue()); } @@ -253,7 +253,7 @@ public void testMaxCharactersForAmount() { when(offerBook.getOfferBookListItems()).thenReturn(offerBookListItems); final OfferBookViewModel model = new OfferBookViewModel(null, openOfferManager, offerBook, empty, null, null, null, - null, null, null, null, getPriceUtil(), coinFormatter, new BsqFormatter()); + null, null, null, getPriceUtil(), null, coinFormatter, new BsqFormatter()); model.activate(); assertEquals(6, model.maxPlacesForAmount.intValue()); @@ -271,7 +271,7 @@ public void testMaxCharactersForAmountRange() { when(offerBook.getOfferBookListItems()).thenReturn(offerBookListItems); final OfferBookViewModel model = new OfferBookViewModel(null, openOfferManager, offerBook, empty, null, null, null, - null, null, null, null, getPriceUtil(), coinFormatter, new BsqFormatter()); + null, null, null, getPriceUtil(), null, coinFormatter, new BsqFormatter()); model.activate(); assertEquals(15, model.maxPlacesForAmount.intValue()); @@ -290,7 +290,7 @@ public void testMaxCharactersForVolumeWithNoOffes() { when(offerBook.getOfferBookListItems()).thenReturn(offerBookListItems); final OfferBookViewModel model = new OfferBookViewModel(null, null, offerBook, empty, null, null, null, - null, null, null, null, getPriceUtil(), coinFormatter, new BsqFormatter()); + null, null, null, getPriceUtil(), null, coinFormatter, new BsqFormatter()); assertEquals(0, model.maxPlacesForVolume.intValue()); } @@ -304,7 +304,7 @@ public void testMaxCharactersForVolume() { when(offerBook.getOfferBookListItems()).thenReturn(offerBookListItems); final OfferBookViewModel model = new OfferBookViewModel(null, openOfferManager, offerBook, empty, null, null, null, - null, null, null, null, getPriceUtil(), coinFormatter, new BsqFormatter()); + null, null, null, getPriceUtil(), null, coinFormatter, new BsqFormatter()); model.activate(); assertEquals(5, model.maxPlacesForVolume.intValue()); @@ -322,7 +322,7 @@ public void testMaxCharactersForVolumeRange() { when(offerBook.getOfferBookListItems()).thenReturn(offerBookListItems); final OfferBookViewModel model = new OfferBookViewModel(null, openOfferManager, offerBook, empty, null, null, null, - null, null, null, null, getPriceUtil(), coinFormatter, new BsqFormatter()); + null, null, null, getPriceUtil(), null, coinFormatter, new BsqFormatter()); model.activate(); assertEquals(9, model.maxPlacesForVolume.intValue()); @@ -341,7 +341,7 @@ public void testMaxCharactersForPriceWithNoOffers() { when(offerBook.getOfferBookListItems()).thenReturn(offerBookListItems); final OfferBookViewModel model = new OfferBookViewModel(null, null, offerBook, empty, null, null, null, - null, null, null, null, getPriceUtil(), coinFormatter, new BsqFormatter()); + null, null, null, getPriceUtil(), null, coinFormatter, new BsqFormatter()); assertEquals(0, model.maxPlacesForPrice.intValue()); } @@ -355,7 +355,7 @@ public void testMaxCharactersForPrice() { when(offerBook.getOfferBookListItems()).thenReturn(offerBookListItems); final OfferBookViewModel model = new OfferBookViewModel(null, openOfferManager, offerBook, empty, null, null, null, - null, null, null, null, getPriceUtil(), coinFormatter, new BsqFormatter()); + null, null, null, getPriceUtil(), null, coinFormatter, new BsqFormatter()); model.activate(); assertEquals(7, model.maxPlacesForPrice.intValue()); @@ -373,7 +373,7 @@ public void testMaxCharactersForPriceDistanceWithNoOffers() { when(offerBook.getOfferBookListItems()).thenReturn(offerBookListItems); final OfferBookViewModel model = new OfferBookViewModel(null, null, offerBook, empty, null, null, null, - null, null, null, null, getPriceUtil(), coinFormatter, new BsqFormatter()); + null, null, null, getPriceUtil(), null, coinFormatter, new BsqFormatter()); assertEquals(0, model.maxPlacesForMarketPriceMargin.intValue()); } @@ -401,7 +401,7 @@ public void testMaxCharactersForPriceDistance() { offerBookListItems.addAll(item1, item2); final OfferBookViewModel model = new OfferBookViewModel(null, openOfferManager, offerBook, empty, null, null, priceFeedService, - null, null, null, null, getPriceUtil(), coinFormatter, new BsqFormatter()); + null, null, null, getPriceUtil(), null, coinFormatter, new BsqFormatter()); model.activate(); assertEquals(8, model.maxPlacesForMarketPriceMargin.intValue()); //" (1.97%)" @@ -422,7 +422,7 @@ public void testGetPrice() { when(priceFeedService.getMarketPrice(anyString())).thenReturn(new MarketPrice("USD", 12684.0450, Instant.now().getEpochSecond(), true)); final OfferBookViewModel model = new OfferBookViewModel(null, openOfferManager, offerBook, empty, null, null, null, - null, null, null, null, getPriceUtil(), coinFormatter, new BsqFormatter()); + null, null, null, getPriceUtil(), null, coinFormatter, new BsqFormatter()); final OfferBookListItem item = make(btcBuyItem.but( with(useMarketBasedPrice, true), diff --git a/proto/src/main/proto/pb.proto b/proto/src/main/proto/pb.proto index a5934665b67..49681858e1b 100644 --- a/proto/src/main/proto/pb.proto +++ b/proto/src/main/proto/pb.proto @@ -158,6 +158,7 @@ message OfferAvailabilityRequest { int64 takers_trade_price = 3; repeated int32 supported_capabilities = 4; string uid = 5; + bool is_taker_api_user = 6; } message OfferAvailabilityResponse { @@ -914,6 +915,8 @@ enum AvailabilityResult { USER_IGNORED = 8; MISSING_MANDATORY_CAPABILITY = 9; NO_REFUND_AGENTS = 10; + UNCONF_TX_LIMIT_HIT = 11; + MAKER_DENIED_API_USER = 12; } /////////////////////////////////////////////////////////////////////////////////////////// From 58a1f9c40248ad310b32b2941fa585bda540d306 Mon Sep 17 00:00:00 2001 From: chimp1984 Date: Mon, 4 Jan 2021 15:34:42 -0500 Subject: [PATCH 8/9] Remove getOffersAvailableForTaker method Add offerFilter.canTakeOffer to getOffer and getOffers --- core/src/main/java/bisq/core/api/CoreApi.java | 9 --------- .../main/java/bisq/core/api/CoreOffersService.java | 11 ++--------- 2 files changed, 2 insertions(+), 18 deletions(-) diff --git a/core/src/main/java/bisq/core/api/CoreApi.java b/core/src/main/java/bisq/core/api/CoreApi.java index caa177664ca..fdf8c05842b 100644 --- a/core/src/main/java/bisq/core/api/CoreApi.java +++ b/core/src/main/java/bisq/core/api/CoreApi.java @@ -112,15 +112,6 @@ public List getOffers(String direction, String currencyCode) { return coreOffersService.getOffers(direction, currencyCode); } - /** - * @param direction The offer direction - * @param currencyCode The offer currency - * @return Returns the offers which can be taken - */ - List getOffersAvailableForTaker(String direction, String currencyCode) { - return coreOffersService.getOffersAvailableForTaker(direction, currencyCode, true); - } - public void createAnPlaceOffer(String currencyCode, String directionAsString, String priceAsString, diff --git a/core/src/main/java/bisq/core/api/CoreOffersService.java b/core/src/main/java/bisq/core/api/CoreOffersService.java index b8a4ef8758e..dd30926c756 100644 --- a/core/src/main/java/bisq/core/api/CoreOffersService.java +++ b/core/src/main/java/bisq/core/api/CoreOffersService.java @@ -76,16 +76,14 @@ public CoreOffersService(CreateOfferService createOfferService, this.offerFilter = offerFilter; } - // TODO should we add a check for offerFilter.canTakeOffer? Offer getOffer(String id) { return offerBookService.getOffers().stream() .filter(o -> o.getId().equals(id)) + .filter(o -> offerFilter.canTakeOffer(o, true).isValid()) .findAny().orElseThrow(() -> new IllegalStateException(format("offer with id '%s' not found", id))); } - // TODO returns all offers also those which cannot be taken. Should we use the filter from - // getOffersAvailableForTaker here and remove the getOffersAvailableForTaker method? List getOffers(String direction, String currencyCode) { List offers = offerBookService.getOffers().stream() .filter(o -> { @@ -94,6 +92,7 @@ List getOffers(String direction, String currencyCode) { .equalsIgnoreCase(currencyCode); return offerOfWantedDirection && offerInWantedCurrency; }) + .filter(offer -> offerFilter.canTakeOffer(offer, true).isValid()) .collect(Collectors.toList()); // A buyer probably wants to see sell orders in price ascending order. @@ -106,12 +105,6 @@ List getOffers(String direction, String currencyCode) { return offers; } - List getOffersAvailableForTaker(String direction, String currencyCode, boolean isTakerApiUser) { - return getOffers(direction, currencyCode).stream() - .filter(offer -> offerFilter.canTakeOffer(offer, isTakerApiUser).isValid()) - .collect(Collectors.toList()); - } - // Create and place new offer. void createAndPlaceOffer(String currencyCode, String directionAsString, From db6722b335b5cc80dc4fcc92e677d1f8b3926ead Mon Sep 17 00:00:00 2001 From: chimp1984 Date: Tue, 5 Jan 2021 10:19:21 -0500 Subject: [PATCH 9/9] Revert most changes with applying offerFilter. Leave it to @ghubstan to implement it. --- core/src/main/java/bisq/core/api/CoreApi.java | 1 - core/src/main/java/bisq/core/api/CoreOffersService.java | 8 +------- core/src/main/java/bisq/core/api/CoreTradesService.java | 3 +-- 3 files changed, 2 insertions(+), 10 deletions(-) diff --git a/core/src/main/java/bisq/core/api/CoreApi.java b/core/src/main/java/bisq/core/api/CoreApi.java index fdf8c05842b..4dffaee9760 100644 --- a/core/src/main/java/bisq/core/api/CoreApi.java +++ b/core/src/main/java/bisq/core/api/CoreApi.java @@ -202,7 +202,6 @@ public void takeOffer(String offerId, coreTradesService.takeOffer(offer, paymentAccountId, takerFeeCurrencyCode, - true, resultHandler); } diff --git a/core/src/main/java/bisq/core/api/CoreOffersService.java b/core/src/main/java/bisq/core/api/CoreOffersService.java index dd30926c756..0764d33f078 100644 --- a/core/src/main/java/bisq/core/api/CoreOffersService.java +++ b/core/src/main/java/bisq/core/api/CoreOffersService.java @@ -22,7 +22,6 @@ import bisq.core.offer.CreateOfferService; import bisq.core.offer.Offer; import bisq.core.offer.OfferBookService; -import bisq.core.offer.OfferFilter; import bisq.core.offer.OfferUtil; import bisq.core.offer.OpenOfferManager; import bisq.core.payment.PaymentAccount; @@ -59,27 +58,23 @@ class CoreOffersService { private final OpenOfferManager openOfferManager; private final OfferUtil offerUtil; private final User user; - private final OfferFilter offerFilter; @Inject public CoreOffersService(CreateOfferService createOfferService, OfferBookService offerBookService, OpenOfferManager openOfferManager, OfferUtil offerUtil, - User user, - OfferFilter offerFilter) { + User user) { this.createOfferService = createOfferService; this.offerBookService = offerBookService; this.openOfferManager = openOfferManager; this.offerUtil = offerUtil; this.user = user; - this.offerFilter = offerFilter; } Offer getOffer(String id) { return offerBookService.getOffers().stream() .filter(o -> o.getId().equals(id)) - .filter(o -> offerFilter.canTakeOffer(o, true).isValid()) .findAny().orElseThrow(() -> new IllegalStateException(format("offer with id '%s' not found", id))); } @@ -92,7 +87,6 @@ List getOffers(String direction, String currencyCode) { .equalsIgnoreCase(currencyCode); return offerOfWantedDirection && offerInWantedCurrency; }) - .filter(offer -> offerFilter.canTakeOffer(offer, true).isValid()) .collect(Collectors.toList()); // A buyer probably wants to see sell orders in price ascending order. diff --git a/core/src/main/java/bisq/core/api/CoreTradesService.java b/core/src/main/java/bisq/core/api/CoreTradesService.java index b4d7fcef188..b15ad6f48b8 100644 --- a/core/src/main/java/bisq/core/api/CoreTradesService.java +++ b/core/src/main/java/bisq/core/api/CoreTradesService.java @@ -82,7 +82,6 @@ public CoreTradesService(CoreWalletsService coreWalletsService, void takeOffer(Offer offer, String paymentAccountId, String takerFeeCurrencyCode, - boolean isTakerApiUser, Consumer resultHandler) { coreWalletsService.verifyWalletsAreAvailable(); coreWalletsService.verifyEncryptedWalletIsUnlocked(); @@ -109,7 +108,7 @@ void takeOffer(Offer offer, offer, paymentAccountId, useSavingsWallet, - isTakerApiUser, + true, resultHandler::accept, errorMessage -> { log.error(errorMessage);