Skip to content

Commit

Permalink
Prevent seller with insufficient reputation from taking an offer
Browse files Browse the repository at this point in the history
  • Loading branch information
axpoems committed Jan 22, 2025
1 parent ef12d9b commit 86b693d
Show file tree
Hide file tree
Showing 3 changed files with 33 additions and 83 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,6 @@
import java.util.function.Predicate;
import java.util.stream.Collectors;

import static bisq.bisq_easy.BisqEasyTradeAmountLimits.Result.*;
import static bisq.chat.ChatMessageType.TAKE_BISQ_EASY_OFFER;
import static com.google.common.base.Preconditions.checkArgument;

Expand Down Expand Up @@ -315,81 +314,41 @@ public void onTakeOffer(BisqEasyOfferbookMessage bisqEasyOfferbookMessage) {
return;
}

Optional<BisqEasyTradeAmountLimits.Result> limitForMinAmount = BisqEasyTradeAmountLimits.checkOfferAmountLimitForMinAmount(reputationService,
userIdentityService,
userProfileService,
marketPriceService,
bisqEasyOffer);
Optional<BisqEasyTradeAmountLimits.Result> limitForMaxAmount = BisqEasyTradeAmountLimits.checkOfferAmountLimitForMaxOrFixedAmount(reputationService,
userIdentityService,
userProfileService,
marketPriceService,
bisqEasyOffer);
if (limitForMaxAmount.isPresent()) {
BisqEasyTradeAmountLimits.Result maxAmountResult = limitForMaxAmount.get();
BisqEasyTradeAmountLimits.Result minAmountResult = limitForMinAmount.orElse(maxAmountResult);
Optional<Long> requiredReputationScoreForMaxOrFixedAmount = BisqEasyTradeAmountLimits.findRequiredReputationScoreForMaxOrFixedAmount(marketPriceService, bisqEasyOffer);
Optional<Long> requiredReputationScoreForMinAmount = BisqEasyTradeAmountLimits.findRequiredReputationScoreForMinAmount(marketPriceService, bisqEasyOffer);
if (requiredReputationScoreForMaxOrFixedAmount.isPresent()) {
Long requiredReputationScoreForMaxOrFixed = requiredReputationScoreForMaxOrFixedAmount.get();
Long requiredReputationScoreForMinOrFixed = requiredReputationScoreForMinAmount.orElse(requiredReputationScoreForMaxOrFixed);
String minFiatAmount = OfferAmountFormatter.formatQuoteSideMinOrFixedAmount(marketPriceService, bisqEasyOffer, true);
String maxFiatAmount = OfferAmountFormatter.formatQuoteSideMaxOrFixedAmount(marketPriceService, bisqEasyOffer, true);
long requiredReputationScoreForMinAmount = minAmountResult.getRequiredReputationScore();
long requiredReputationScoreForMaxAmount = maxAmountResult.getRequiredReputationScore();
long sellersScore;
if (bisqEasyOffer.getTakersDirection().isBuy()) {
// I am as taker the buyer. We check if sellers offer matches the limits
// I am as taker the buyer. We check if seller has the required reputation
sellersScore = userProfileService.findUserProfile(bisqEasyOffer.getMakersUserProfileId())
.map(reputationService::getReputationScore)
.map(ReputationScore::getTotalScore)
.orElse(0L);

if (maxAmountResult == SCORE_TOO_LOW) {
if (bisqEasyOffer.getAmountSpec() instanceof RangeAmountSpec) {
if (minAmountResult == SCORE_TOO_LOW) {
// Min amount too high for sellers reputation score
new Popup()
.headline(Res.get("chat.message.takeOffer.buyer.makersReputationScoreTooLow.headline"))
.warning(Res.get("chat.message.takeOffer.buyer.makersReputationScoreTooLow.warning",
sellersScore, requiredReputationScoreForMaxAmount, minFiatAmount))
.closeButtonText(Res.get("chat.message.takeOffer.buyer.makersReputationScoreTooLow.no"))
.secondaryActionButtonText(Res.get("chat.message.takeOffer.buyer.makersReputationScoreTooLow.yes"))
.onSecondaryAction(() -> Navigation.navigateTo(NavigationTarget.TAKE_OFFER, new TakeOfferController.InitData(bisqEasyOffer, Optional.empty())))
.show();
} else {
new Popup()
.headline(Res.get("chat.message.takeOffer.buyer.makersReputationScoreTooLow.headline"))
.warning(Res.get("chat.message.takeOffer.buyer.makersReputationScoreTooLow.warning",
sellersScore, requiredReputationScoreForMaxAmount, maxFiatAmount))
.closeButtonText(Res.get("chat.message.takeOffer.buyer.makersReputationScoreTooLow.no"))
.secondaryActionButtonText(Res.get("chat.message.takeOffer.buyer.makersReputationScoreTooLow.yes"))
.onSecondaryAction(() -> Navigation.navigateTo(NavigationTarget.TAKE_OFFER, new TakeOfferController.InitData(bisqEasyOffer, Optional.empty())))
.show();
}
} else {
// Fixed price offer
new Popup()
.headline(Res.get("chat.message.takeOffer.buyer.makersReputationScoreTooLow.headline"))
.warning(Res.get("chat.message.takeOffer.buyer.makersReputationScoreTooLow.warning",
sellersScore, requiredReputationScoreForMaxAmount, maxFiatAmount))
.closeButtonText(Res.get("chat.message.takeOffer.buyer.makersReputationScoreTooLow.no"))
.secondaryActionButtonText(Res.get("chat.message.takeOffer.buyer.makersReputationScoreTooLow.yes"))
.onSecondaryAction(() -> Navigation.navigateTo(NavigationTarget.TAKE_OFFER, new TakeOfferController.InitData(bisqEasyOffer, Optional.empty())))
.show();
}

} else if (maxAmountResult == MATCH_MIN_SCORE) {
boolean canBuyerTakeOffer = sellersScore >= requiredReputationScoreForMinOrFixed;
if (!canBuyerTakeOffer) {
boolean isAmountRangeOffer = bisqEasyOffer.getAmountSpec() instanceof RangeAmountSpec;
new Popup()
.headline(Res.get("chat.message.takeOffer.buyer.makersReputationTooLowButInLowAmountTolerance.headline"))
.warning(Res.get("chat.message.takeOffer.buyer.makersReputationTooLowButInLowAmountTolerance.warning",
sellersScore, requiredReputationScoreForMaxAmount, maxFiatAmount))
.closeButtonText(Res.get("chat.message.takeOffer.buyer.makersReputationTooLowButInLowAmountTolerance.no"))
.secondaryActionButtonText(Res.get("chat.message.takeOffer.buyer.makersReputationTooLowButInLowAmountTolerance.yes"))
.onSecondaryAction(() -> Navigation.navigateTo(NavigationTarget.TAKE_OFFER, new TakeOfferController.InitData(bisqEasyOffer, Optional.empty())))
.show();
.headline(Res.get("chat.message.takeOffer.buyer.invalidOffer.headline"))
.warning(Res.get(isAmountRangeOffer
? "chat.message.takeOffer.buyer.invalidOffer.rangeAmount.text"
: "chat.message.takeOffer.buyer.invalidOffer.fixedAmount.text",
sellersScore,
isAmountRangeOffer ? requiredReputationScoreForMinOrFixed : requiredReputationScoreForMaxOrFixed,
isAmountRangeOffer ? minFiatAmount : maxFiatAmount))
.closeButtonText(Res.get("action.close"))
.show();
} else {
Navigation.navigateTo(NavigationTarget.TAKE_OFFER, new TakeOfferController.InitData(bisqEasyOffer, Optional.empty()));
}
} else {
// I am as taker the seller. We check if my reputation permits to take the offer
sellersScore = reputationService.getReputationScore(userIdentityService.getSelectedUserIdentity().getUserProfile()).getTotalScore();
if (minAmountResult == SCORE_TOO_LOW) {
boolean canSellerTakeOffer = sellersScore >= requiredReputationScoreForMinOrFixed;
if (!canSellerTakeOffer) {
new Popup()
.headline(Res.get("chat.message.takeOffer.seller.myReputationScoreTooLow.headline"))
.warning(Res.get("chat.message.takeOffer.seller.myReputationScoreTooLow.warning",
Expand All @@ -406,7 +365,8 @@ public void onTakeOffer(BisqEasyOfferbookMessage bisqEasyOfferbookMessage) {
}
}
} else {
log.warn("limitForMinAmount or limitForMaxAmount is not present. limitForMinAmount={}; limitForMaxAmount={}", limitForMinAmount, limitForMaxAmount);
log.warn("requiredReputationScoreForMaxOrFixedAmount is not present. requiredReputationScoreForMaxOrFixedAmount={}; requiredReputationScoreForMinAmount={}",
requiredReputationScoreForMaxOrFixedAmount, requiredReputationScoreForMinAmount);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,8 @@ public static Optional<Result> checkOfferAmountLimitForMinAmount(ReputationServi
});
}



public static Optional<Result> checkOfferAmountLimitForMaxOrFixedAmount(ReputationService reputationService,
UserIdentityService userIdentityService,
UserProfileService userProfileService,
Expand Down Expand Up @@ -157,7 +159,7 @@ public static Optional<Long> findRequiredReputationScoreForMaxOrFixedAmount(Mark
.flatMap(fiatAmount -> findRequiredReputationScoreByFiatAmount(marketPriceService, offer.getMarket(), fiatAmount));
}

private static Optional<Long> findRequiredReputationScoreForMinAmount(MarketPriceService marketPriceService,
public static Optional<Long> findRequiredReputationScoreForMinAmount(MarketPriceService marketPriceService,
BisqEasyOffer offer) {
return OfferAmountUtil.findQuoteSideMinAmount(marketPriceService, offer)
.flatMap(fiatAmount -> findRequiredReputationScoreByFiatAmount(marketPriceService, offer.getMarket(), fiatAmount));
Expand Down Expand Up @@ -232,6 +234,7 @@ public enum Result {
MATCH_MIN_SCORE,
SCORE_TOO_LOW;

// TODO: Remove this. We cannot have mutable properties inside an enum
@Setter
private Long requiredReputationScore;

Expand Down
27 changes: 7 additions & 20 deletions i18n/src/main/resources/chat.properties
Original file line number Diff line number Diff line change
Expand Up @@ -223,26 +223,13 @@ chat.message.takeOffer.seller.myReputationScoreTooLow.warning=Your reputation sc
To learn more about the reputation system, visit: [HYPERLINK:https://bisq.wiki/Reputation].\n\n\
You can read more about how to build up your reputation at ''Reputation/Build Reputation''.

chat.message.takeOffer.buyer.makersReputationScoreTooLow.headline=Security warning
chat.message.takeOffer.buyer.makersReputationScoreTooLow.warning=The seller''s reputation score of {0} is below the required score of {1} for a trade amount of {2}.\n\n\
Bisq Easy''s security model relies on the seller''s reputation as the buyer need to send the fiat currency first. \
If you choose to proceed with taking the offer despite the lack of reputation, ensure you fully understand the risks involved.\n\n\
To learn more about the reputation system, visit: [HYPERLINK:https://bisq.wiki/Reputation].\n\n\
Do you want to continue and take that offer?

chat.message.takeOffer.buyer.makersReputationScoreTooLow.yes=Yes, I understand the risk and want to continue
chat.message.takeOffer.buyer.makersReputationScoreTooLow.no=No, I look for another offer

chat.message.takeOffer.buyer.makersReputationTooLowButInLowAmountTolerance.headline=Please review the risks when taking that offer
chat.message.takeOffer.buyer.makersReputationTooLowButInLowAmountTolerance.warning=The seller''s reputation score is {0}, \
which is below the required score of {1} for a trade amount of {2}.\n\n\
Since the trade amount is relatively low, the reputation requirements have been relaxed. \
However, if you choose to proceed despite the seller''s insufficient reputation, make sure you fully understand the associated risks. \
Bisq Easy''s security model depends on the seller''s reputation, as the buyer is required to send fiat currency first.\n\n\
To learn more about the reputation system, visit: [HYPERLINK:https://bisq.wiki/Reputation].\n\n\
Do you still want to take the offer?
chat.message.takeOffer.buyer.makersReputationTooLowButInLowAmountTolerance.yes=Yes, I understand the risk and want to continue
chat.message.takeOffer.buyer.makersReputationTooLowButInLowAmountTolerance.no=No, I look for another offer
chat.message.takeOffer.buyer.invalidOffer.headline=This offer can't be taken
chat.message.takeOffer.buyer.invalidOffer.fixedAmount.text=The seller''s reputation score of {0} is below the required score of {1} for the trade amount of {2}.\n\n\
Bisq Easy''s security model relies on the seller''s reputation as the buyer need to send the fiat currency first.\n\n\
To learn more about the reputation system, visit: [HYPERLINK:https://bisq.wiki/Reputation].
chat.message.takeOffer.buyer.invalidOffer.rangeAmount.text=The seller''s reputation score of {0} is below the required score of {1} for the minimum of the trade amount range of {2}.\n\n\
Bisq Easy''s security model relies on the seller''s reputation as the buyer need to send the fiat currency first.\n\n\
To learn more about the reputation system, visit: [HYPERLINK:https://bisq.wiki/Reputation].

chat.message.offer.offerAlreadyTaken.warn=You have already taken that offer.
chat.message.delete.differentUserProfile.warn=This chat message was created with another user profile.\n\n\
Expand Down

0 comments on commit 86b693d

Please sign in to comment.