diff --git a/contract/src/main/java/bisq/contract/bisq_easy/BisqEasyContract.java b/contract/src/main/java/bisq/contract/bisq_easy/BisqEasyContract.java index 71ea2a1d0a..3411256b52 100644 --- a/contract/src/main/java/bisq/contract/bisq_easy/BisqEasyContract.java +++ b/contract/src/main/java/bisq/contract/bisq_easy/BisqEasyContract.java @@ -26,6 +26,7 @@ import bisq.offer.payment_method.BitcoinPaymentMethodSpec; import bisq.offer.payment_method.FiatPaymentMethodSpec; import bisq.offer.payment_method.PaymentMethodSpec; +import bisq.offer.price.spec.PriceSpec; import bisq.user.profile.UserProfile; import lombok.EqualsAndHashCode; import lombok.Getter; @@ -42,6 +43,8 @@ public class BisqEasyContract extends TwoPartyContract { protected final BitcoinPaymentMethodSpec baseSidePaymentMethodSpec; protected final FiatPaymentMethodSpec quoteSidePaymentMethodSpec; private final Optional mediator; + private final PriceSpec agreedPriceSpec; + private final long marketPrice; public BisqEasyContract(BisqEasyOffer offer, NetworkId takerNetworkId, @@ -49,7 +52,9 @@ public BisqEasyContract(BisqEasyOffer offer, long quoteSideAmount, BitcoinPaymentMethodSpec baseSidePaymentMethodSpec, FiatPaymentMethodSpec quoteSidePaymentMethodSpec, - Optional mediator) { + Optional mediator, + PriceSpec agreedPriceSpec, + long marketPrice) { this(offer, TradeProtocolType.BISQ_EASY, new Party(Role.TAKER, takerNetworkId), @@ -57,7 +62,9 @@ public BisqEasyContract(BisqEasyOffer offer, quoteSideAmount, baseSidePaymentMethodSpec, quoteSidePaymentMethodSpec, - mediator); + mediator, + agreedPriceSpec, + marketPrice); } private BisqEasyContract(BisqEasyOffer offer, @@ -67,13 +74,17 @@ private BisqEasyContract(BisqEasyOffer offer, long quoteSideAmount, BitcoinPaymentMethodSpec baseSidePaymentMethodSpec, FiatPaymentMethodSpec quoteSidePaymentMethodSpec, - Optional mediator) { + Optional mediator, + PriceSpec agreedPriceSpec, + long marketPrice) { super(offer, protocolType, taker); this.baseSideAmount = baseSideAmount; this.quoteSideAmount = quoteSideAmount; this.baseSidePaymentMethodSpec = baseSidePaymentMethodSpec; this.quoteSidePaymentMethodSpec = quoteSidePaymentMethodSpec; this.mediator = mediator; + this.agreedPriceSpec = agreedPriceSpec; + this.marketPrice = marketPrice; } @Override @@ -82,7 +93,9 @@ public bisq.contract.protobuf.Contract toProto() { .setBaseSideAmount(baseSideAmount) .setQuoteSideAmount(quoteSideAmount) .setBaseSidePaymentMethodSpec(baseSidePaymentMethodSpec.toProto()) - .setQuoteSidePaymentMethodSpec(quoteSidePaymentMethodSpec.toProto()); + .setQuoteSidePaymentMethodSpec(quoteSidePaymentMethodSpec.toProto()) + .setAgreedPriceSpec(agreedPriceSpec.toProto()) + .setMarketPrice(marketPrice); mediator.ifPresent(mediator -> bisqEasyContract.setMediator(mediator.toProto())); var twoPartyContract = getTwoPartyContractBuilder().setBisqEasyContract(bisqEasyContract); return getContractBuilder().setTwoPartyContract(twoPartyContract).build(); @@ -100,6 +113,8 @@ public static BisqEasyContract fromProto(bisq.contract.protobuf.Contract proto) PaymentMethodSpec.protoToFiatPaymentMethodSpec(bisqEasyContractProto.getQuoteSidePaymentMethodSpec()), bisqEasyContractProto.hasMediator() ? Optional.of(UserProfile.fromProto(bisqEasyContractProto.getMediator())) : - Optional.empty()); + Optional.empty(), + PriceSpec.fromProto(bisqEasyContractProto.getAgreedPriceSpec()), + bisqEasyContractProto.getMarketPrice()); } -} \ No newline at end of file +} diff --git a/contract/src/main/proto/contract.proto b/contract/src/main/proto/contract.proto index e7fb44f7c6..7d8df5c4eb 100644 --- a/contract/src/main/proto/contract.proto +++ b/contract/src/main/proto/contract.proto @@ -82,6 +82,8 @@ message BisqEasyContract { offer.PaymentMethodSpec baseSidePaymentMethodSpec = 3; offer.PaymentMethodSpec quoteSidePaymentMethodSpec = 4; optional user.UserProfile mediator = 12; + offer.PriceSpec agreedPriceSpec = 13; + uint64 marketPrice = 14; } diff --git a/desktop/src/main/java/bisq/desktop/main/content/bisq_easy/take_offer/review/TakeOfferReviewController.java b/desktop/src/main/java/bisq/desktop/main/content/bisq_easy/take_offer/review/TakeOfferReviewController.java index b5e98b2d5c..5fc47167cf 100644 --- a/desktop/src/main/java/bisq/desktop/main/content/bisq_easy/take_offer/review/TakeOfferReviewController.java +++ b/desktop/src/main/java/bisq/desktop/main/content/bisq_easy/take_offer/review/TakeOfferReviewController.java @@ -182,7 +182,9 @@ public void doTakeOffer() { model.getTakersBaseSideAmount(), model.getTakersQuoteSideAmount(), bisqEasyOffer.getBaseSidePaymentMethodSpecs().get(0), - model.getFiatPaymentMethodSpec()); + model.getFiatPaymentMethodSpec(), + model.getSellersPriceSpec(), + model.getMarketPrice()); model.setBisqEasyTrade(bisqEasyTrade); @@ -245,9 +247,12 @@ void onShowOpenTrades() { } private void applyPriceDetails(PriceSpec priceSpec, Market market) { - Optional marketPriceQuote = marketPriceService.findMarketPrice(market) - .map(MarketPrice::getPriceQuote); - String marketPrice = marketPriceQuote + Optional marketPrice = marketPriceService.findMarketPrice(market); + if (marketPrice.isPresent()) { + model.setMarketPrice(marketPrice.get().getPriceQuote().getValue()); + } + Optional marketPriceQuote = marketPrice.map(MarketPrice::getPriceQuote); + String marketPriceAsString = marketPriceQuote .map(PriceFormatter::formatWithCode) .orElse(Res.get("data.na")); Optional percentFromMarketPrice; @@ -255,7 +260,7 @@ private void applyPriceDetails(PriceSpec priceSpec, Market market) { double percent = percentFromMarketPrice.orElse(0d); if ((priceSpec instanceof FloatPriceSpec || priceSpec instanceof MarketPriceSpec) && percent == 0) { - model.setPriceDetails(Res.get("bisqEasy.tradeWizard.review.priceDetails.seller", marketPrice)); + model.setPriceDetails(Res.get("bisqEasy.tradeWizard.review.priceDetails.seller", marketPriceAsString)); } else { String aboveOrBelow = percent > 0 ? Res.get("offer.price.above") : @@ -264,13 +269,13 @@ private void applyPriceDetails(PriceSpec priceSpec, Market market) { .orElse(Res.get("data.na")); if (priceSpec instanceof FloatPriceSpec) { model.setPriceDetails(Res.get("bisqEasy.tradeWizard.review.priceDetails.seller.float", - percentAsString, aboveOrBelow, marketPrice)); + percentAsString, aboveOrBelow, marketPriceAsString)); } else { if (percent == 0) { - model.setPriceDetails(Res.get("bisqEasy.tradeWizard.review.priceDetails.seller.fix.atMarket", marketPrice)); + model.setPriceDetails(Res.get("bisqEasy.tradeWizard.review.priceDetails.seller.fix.atMarket", marketPriceAsString)); } else { model.setPriceDetails(Res.get("bisqEasy.tradeWizard.review.priceDetails.seller.fix", - percentAsString, aboveOrBelow, marketPrice)); + percentAsString, aboveOrBelow, marketPriceAsString)); } } } diff --git a/desktop/src/main/java/bisq/desktop/main/content/bisq_easy/take_offer/review/TakeOfferReviewModel.java b/desktop/src/main/java/bisq/desktop/main/content/bisq_easy/take_offer/review/TakeOfferReviewModel.java index 96faedd2e3..6288963a39 100644 --- a/desktop/src/main/java/bisq/desktop/main/content/bisq_easy/take_offer/review/TakeOfferReviewModel.java +++ b/desktop/src/main/java/bisq/desktop/main/content/bisq_easy/take_offer/review/TakeOfferReviewModel.java @@ -61,4 +61,6 @@ class TakeOfferReviewModel implements Model { private String fee; @Setter private String feeDetails; -} \ No newline at end of file + @Setter + private long marketPrice; +} diff --git a/desktop/src/main/java/bisq/desktop/main/content/bisq_easy/trade_wizard/review/TradeWizardReviewController.java b/desktop/src/main/java/bisq/desktop/main/content/bisq_easy/trade_wizard/review/TradeWizardReviewController.java index 65bd48e0ae..456542b142 100644 --- a/desktop/src/main/java/bisq/desktop/main/content/bisq_easy/trade_wizard/review/TradeWizardReviewController.java +++ b/desktop/src/main/java/bisq/desktop/main/content/bisq_easy/trade_wizard/review/TradeWizardReviewController.java @@ -404,7 +404,9 @@ public void takeOffer() { model.getFixBaseSideAmount(), model.getFixQuoteSideAmount(), bisqEasyOffer.getBaseSidePaymentMethodSpecs().get(0), - paymentMethodSpec); + paymentMethodSpec, + model.getPriceSpec(), + model.getMarketPrice()); model.setBisqEasyTrade(bisqEasyTrade); @@ -469,12 +471,16 @@ private void applyHeaderPaymentMethod() { } private void applyPriceDetails(Direction direction, PriceSpec priceSpec, Market market) { + Optional marketPrice = marketPriceService.findMarketPrice(market); + if (marketPrice.isPresent()) { + model.setMarketPrice(marketPrice.get().getPriceQuote().getValue()); + } if (model.isCreateOfferMode() && direction.isBuy()) { model.setPriceDetails(Res.get("bisqEasy.tradeWizard.review.priceDetails.buyer")); } else { Optional marketPriceQuote = marketPriceService.findMarketPrice(market) .map(MarketPrice::getPriceQuote); - String marketPrice = marketPriceQuote + String marketPriceAsString = marketPriceQuote .map(PriceFormatter::formatWithCode) .orElse(Res.get("data.na")); Optional percentFromMarketPrice; @@ -482,7 +488,7 @@ private void applyPriceDetails(Direction direction, PriceSpec priceSpec, Market double percent = percentFromMarketPrice.orElse(0d); if ((priceSpec instanceof FloatPriceSpec || priceSpec instanceof MarketPriceSpec) && percent == 0) { - model.setPriceDetails(Res.get("bisqEasy.tradeWizard.review.priceDetails.seller", marketPrice)); + model.setPriceDetails(Res.get("bisqEasy.tradeWizard.review.priceDetails.seller", marketPriceAsString)); } else { String aboveOrBelow = percent > 0 ? Res.get("offer.price.above") : @@ -491,13 +497,13 @@ private void applyPriceDetails(Direction direction, PriceSpec priceSpec, Market .orElse(Res.get("data.na")); if (priceSpec instanceof FloatPriceSpec) { model.setPriceDetails(Res.get("bisqEasy.tradeWizard.review.priceDetails.seller.float", - percentAsString, aboveOrBelow, marketPrice)); + percentAsString, aboveOrBelow, marketPriceAsString)); } else { if (percent == 0) { - model.setPriceDetails(Res.get("bisqEasy.tradeWizard.review.priceDetails.seller.fix.atMarket", marketPrice)); + model.setPriceDetails(Res.get("bisqEasy.tradeWizard.review.priceDetails.seller.fix.atMarket", marketPriceAsString)); } else { model.setPriceDetails(Res.get("bisqEasy.tradeWizard.review.priceDetails.seller.fix", - percentAsString, aboveOrBelow, marketPrice)); + percentAsString, aboveOrBelow, marketPriceAsString)); } } } diff --git a/desktop/src/main/java/bisq/desktop/main/content/bisq_easy/trade_wizard/review/TradeWizardReviewModel.java b/desktop/src/main/java/bisq/desktop/main/content/bisq_easy/trade_wizard/review/TradeWizardReviewModel.java index 4ced71bb32..72f68f7aa3 100644 --- a/desktop/src/main/java/bisq/desktop/main/content/bisq_easy/trade_wizard/review/TradeWizardReviewModel.java +++ b/desktop/src/main/java/bisq/desktop/main/content/bisq_easy/trade_wizard/review/TradeWizardReviewModel.java @@ -91,6 +91,8 @@ class TradeWizardReviewModel implements Model { private final ObservableList takersPaymentMethods = FXCollections.observableArrayList(); private final BooleanProperty showCreateOfferSuccess = new SimpleBooleanProperty(); private final BooleanProperty showTakeOfferSuccess = new SimpleBooleanProperty(); + @Setter + private long marketPrice; public void reset() { isCreateOfferMode = false; @@ -119,5 +121,6 @@ public void reset() { takersPaymentMethods.clear(); showCreateOfferSuccess.set(false); showTakeOfferSuccess.set(false); + marketPrice = 0; } -} \ No newline at end of file +} diff --git a/trade/src/main/java/bisq/trade/bisq_easy/BisqEasyTradeService.java b/trade/src/main/java/bisq/trade/bisq_easy/BisqEasyTradeService.java index f08932acca..431797391f 100644 --- a/trade/src/main/java/bisq/trade/bisq_easy/BisqEasyTradeService.java +++ b/trade/src/main/java/bisq/trade/bisq_easy/BisqEasyTradeService.java @@ -28,6 +28,7 @@ import bisq.offer.bisq_easy.BisqEasyOffer; import bisq.offer.payment_method.BitcoinPaymentMethodSpec; import bisq.offer.payment_method.FiatPaymentMethodSpec; +import bisq.offer.price.spec.PriceSpec; import bisq.persistence.Persistence; import bisq.persistence.PersistenceClient; import bisq.trade.ServiceProvider; @@ -230,7 +231,9 @@ public BisqEasyTrade onTakeOffer(Identity takerIdentity, Monetary baseSideAmount, Monetary quoteSideAmount, BitcoinPaymentMethodSpec bitcoinPaymentMethodSpec, - FiatPaymentMethodSpec fiatPaymentMethodSpec) throws TradeException { + FiatPaymentMethodSpec fiatPaymentMethodSpec, + PriceSpec agreedPriceSpec, + long marketPrice) throws TradeException { Optional mediator = serviceProvider.getSupportService().getMediationService().selectMediator(bisqEasyOffer.getMakersUserProfileId(), takerIdentity.getId()); NetworkId takerNetworkId = takerIdentity.getNetworkId(); BisqEasyContract contract = new BisqEasyContract(bisqEasyOffer, @@ -239,7 +242,9 @@ public BisqEasyTrade onTakeOffer(Identity takerIdentity, quoteSideAmount.getValue(), bitcoinPaymentMethodSpec, fiatPaymentMethodSpec, - mediator); + mediator, + agreedPriceSpec, + marketPrice); boolean isBuyer = bisqEasyOffer.getTakersDirection().isBuy(); BisqEasyTrade bisqEasyTrade = new BisqEasyTrade(isBuyer, true, takerIdentity, contract, takerNetworkId); diff --git a/trade/src/main/java/bisq/trade/bisq_easy/protocol/messages/BisqEasyTakeOfferRequestHandler.java b/trade/src/main/java/bisq/trade/bisq_easy/protocol/messages/BisqEasyTakeOfferRequestHandler.java index b71dc1f5f5..3453ed3208 100644 --- a/trade/src/main/java/bisq/trade/bisq_easy/protocol/messages/BisqEasyTakeOfferRequestHandler.java +++ b/trade/src/main/java/bisq/trade/bisq_easy/protocol/messages/BisqEasyTakeOfferRequestHandler.java @@ -19,12 +19,14 @@ import bisq.chat.bisqeasy.offerbook.BisqEasyOfferbookChannelService; import bisq.common.fsm.Event; +import bisq.common.monetary.Monetary; import bisq.common.util.StringUtils; import bisq.contract.ContractService; import bisq.contract.ContractSignatureData; import bisq.contract.bisq_easy.BisqEasyContract; import bisq.offer.Offer; import bisq.offer.bisq_easy.BisqEasyOffer; +import bisq.offer.price.PriceUtil; import bisq.trade.ServiceProvider; import bisq.trade.bisq_easy.BisqEasyTrade; import bisq.trade.protocol.events.TradeMessageHandler; @@ -101,18 +103,7 @@ protected void verifyMessage(BisqEasyTakeOfferRequest message) { checkArgument(message.getSender().equals(takersContract.getTaker().getNetworkId())); - // FIXME If there is no market price available we get a NP in the code below - /* Monetary baseSideMinAmount = OfferAmountUtil.findBaseSideMinOrFixedAmount(serviceProvider.getBondedRolesService().getMarketPriceService(), takersOffer).orElseThrow(); - Monetary baseSideMaxAmount = OfferAmountUtil.findBaseSideMaxOrFixedAmount(serviceProvider.getBondedRolesService().getMarketPriceService(), takersOffer).orElseThrow();*/ - - //todo add tolerance as market price might be a bit off - // checkArgument(takersContract.getBaseSideAmount() >= baseSideMinAmount.getValue()); - // checkArgument(takersContract.getBaseSideAmount() <= baseSideMaxAmount.getValue()); - - /* Monetary quoteSideMinAmount = OfferAmountUtil.findQuoteSideMinOrFixedAmount(serviceProvider.getBondedRolesService().getMarketPriceService(), takersOffer).orElseThrow(); - Monetary quoteSideMaxAmount = OfferAmountUtil.findQuoteSideMaxOrFixedAmount(serviceProvider.getBondedRolesService().getMarketPriceService(), takersOffer).orElseThrow();*/ - // checkArgument(takersContract.getQuoteSideAmount() >= quoteSideMinAmount.getValue()); - // checkArgument(takersContract.getQuoteSideAmount() <= quoteSideMaxAmount.getValue()); + validateAmount(takersOffer, takersContract); checkArgument(takersOffer.getBaseSidePaymentMethodSpecs().contains(takersContract.getBaseSidePaymentMethodSpec())); checkArgument(takersOffer.getQuoteSidePaymentMethodSpecs().contains(takersContract.getQuoteSidePaymentMethodSpec())); @@ -125,4 +116,26 @@ private void commitToModel(ContractSignatureData takersContractSignatureData, Co trade.getTaker().getContractSignatureData().set(takersContractSignatureData); trade.getMaker().getContractSignatureData().set(makersContractSignatureData); } -} \ No newline at end of file + + private void validateAmount(BisqEasyOffer takersOffer, BisqEasyContract takersContract) { + Optional amount = getAmount(takersOffer, takersContract); + checkArgument(amount.isPresent(), "No market price available for validation."); + + double tolerancePercentage = 0.01; + long tolerance = (long) (amount.get().getValue() * tolerancePercentage); + long minAmountWithTolerance = amount.get().getValue() - tolerance; + long maxAmountWithTolerance = amount.get().getValue() + tolerance; + + long takersAmount = takersContract.getBaseSideAmount(); + String errorMsg = "Market price deviation is too big."; + checkArgument(takersAmount >= minAmountWithTolerance, errorMsg); + checkArgument(takersAmount <= maxAmountWithTolerance, errorMsg); + } + + private Optional getAmount(BisqEasyOffer takersOffer, BisqEasyContract takersContract) { + return PriceUtil.findQuote(serviceProvider.getBondedRolesService().getMarketPriceService(), + takersContract.getAgreedPriceSpec(), takersOffer.getMarket()) + .map(quote -> quote.toBaseSideMonetary(Monetary.from(takersContract.getQuoteSideAmount(), + takersOffer.getMarket().getQuoteCurrencyCode()))); + } +}