From 849a1c9c559907026ebf48c05ff9bbf3ad5dc4cf Mon Sep 17 00:00:00 2001 From: woodser Date: Sat, 9 Sep 2023 09:31:51 -0400 Subject: [PATCH] automatically adjust offer and trade amount for grpc fixed-price offers --- .../haveno/core/api/CoreTradesService.java | 22 +++++++++++++++++ .../haveno/core/offer/CreateOfferService.java | 24 ++++++++++++------- .../offer/takeoffer/TakeOfferViewModel.java | 4 ++-- 3 files changed, 39 insertions(+), 11 deletions(-) diff --git a/core/src/main/java/haveno/core/api/CoreTradesService.java b/core/src/main/java/haveno/core/api/CoreTradesService.java index db9f0666..25a97420 100644 --- a/core/src/main/java/haveno/core/api/CoreTradesService.java +++ b/core/src/main/java/haveno/core/api/CoreTradesService.java @@ -20,7 +20,10 @@ package haveno.core.api; import haveno.common.handlers.ErrorMessageHandler; import haveno.common.handlers.ResultHandler; import haveno.core.offer.Offer; +import haveno.core.offer.OfferDirection; +import haveno.core.offer.OfferUtil; import haveno.core.offer.takeoffer.TakeOfferModel; +import haveno.core.payment.payload.PaymentMethod; import haveno.core.support.messages.ChatMessage; import haveno.core.support.traderchat.TradeChatSession; import haveno.core.support.traderchat.TraderChatManager; @@ -32,6 +35,7 @@ import haveno.core.trade.TradeUtil; import haveno.core.trade.protocol.BuyerProtocol; import haveno.core.trade.protocol.SellerProtocol; import haveno.core.user.User; +import haveno.core.util.coin.CoinUtil; import haveno.core.util.validation.BtcAddressValidator; import haveno.core.xmr.model.AddressEntry; import haveno.core.xmr.wallet.BtcWalletService; @@ -64,6 +68,7 @@ class CoreTradesService { private final TradeManager tradeManager; private final TraderChatManager traderChatManager; private final TradeUtil tradeUtil; + private final OfferUtil offerUtil; private final User user; @Inject @@ -75,6 +80,7 @@ class CoreTradesService { TradeManager tradeManager, TraderChatManager traderChatManager, TradeUtil tradeUtil, + OfferUtil offerUtil, User user) { this.coreContext = coreContext; this.coreWalletsService = coreWalletsService; @@ -84,6 +90,7 @@ class CoreTradesService { this.tradeManager = tradeManager; this.traderChatManager = traderChatManager; this.tradeUtil = tradeUtil; + this.offerUtil = offerUtil; this.user = user; } @@ -105,6 +112,21 @@ class CoreTradesService { // default to offer amount BigInteger amount = amountAsLong == 0 ? offer.getAmount() : BigInteger.valueOf(amountAsLong); + // adjust amount for fixed-price offer (based on TakeOfferViewModel) + String currencyCode = offer.getCurrencyCode(); + OfferDirection direction = offer.getOfferPayload().getDirection(); + long maxTradeLimit = offerUtil.getMaxTradeLimit(paymentAccount, currencyCode, direction); + if (offer.getPrice() != null) { + if (PaymentMethod.isRoundedForAtmCash(paymentAccount.getPaymentMethod().getId())) { + amount = CoinUtil.getRoundedAtmCashAmount(amount, offer.getPrice(), maxTradeLimit); + } else if (offer.isTraditionalOffer() + && !amount.equals(offer.getMinAmount()) && !amount.equals(amount)) { + // We only apply the rounding if the amount is variable (minAmount is lower as amount). + // Otherwise we could get an amount lower then the minAmount set by rounding + amount = CoinUtil.getRoundedAmount(amount, offer.getPrice(), maxTradeLimit, offer.getCurrencyCode(), offer.getPaymentMethodId()); + } + } + // synchronize access to take offer model // TODO (woodser): to avoid synchronizing, don't use stateful model BigInteger takerFee; BigInteger fundsNeededForTrade; diff --git a/core/src/main/java/haveno/core/offer/CreateOfferService.java b/core/src/main/java/haveno/core/offer/CreateOfferService.java index 448a7ef2..2b53a4f8 100644 --- a/core/src/main/java/haveno/core/offer/CreateOfferService.java +++ b/core/src/main/java/haveno/core/offer/CreateOfferService.java @@ -100,7 +100,7 @@ public class CreateOfferService { String currencyCode, BigInteger amount, BigInteger minAmount, - Price price, + Price fixedPrice, boolean useMarketBasedPrice, double marketPriceMargin, double buyerSecurityDepositAsDouble, @@ -109,7 +109,7 @@ public class CreateOfferService { log.info("create and get offer with offerId={}, " + "currencyCode={}, " + "direction={}, " + - "price={}, " + + "fixedPrice={}, " + "useMarketBasedPrice={}, " + "marketPriceMargin={}, " + "amount={}, " + @@ -118,7 +118,7 @@ public class CreateOfferService { offerId, currencyCode, direction, - price == null ? null : price.getValue(), + fixedPrice == null ? null : fixedPrice.getValue(), useMarketBasedPrice, marketPriceMargin, amount, @@ -126,24 +126,31 @@ public class CreateOfferService { buyerSecurityDepositAsDouble); // verify fixed price xor market price with margin - if (price != null) { + if (fixedPrice != null) { if (useMarketBasedPrice) throw new IllegalArgumentException("Can create offer with fixed price or floating market price but not both"); if (marketPriceMargin != 0) throw new IllegalArgumentException("Cannot set market price margin with fixed price"); } long creationTime = new Date().getTime(); NodeAddress makerAddress = p2PService.getAddress(); - boolean useMarketBasedPriceValue = price == null && + boolean useMarketBasedPriceValue = fixedPrice == null && useMarketBasedPrice && isMarketPriceAvailable(currencyCode) && !PaymentMethod.isFixedPriceOnly(paymentAccount.getPaymentMethod().getId()); // verify price - if (price == null && !useMarketBasedPriceValue) { - throw new IllegalArgumentException("Must provide fixed price because market price is unavailable"); + if (fixedPrice == null && !useMarketBasedPriceValue) { + throw new IllegalArgumentException("Must provide fixed price"); } - long priceAsLong = price != null ? price.getValue() : 0L; + // adjust amount and min amount for fixed-price offer + long maxTradeLimit = offerUtil.getMaxTradeLimit(paymentAccount, currencyCode, direction); + if (fixedPrice != null) { + amount = CoinUtil.getRoundedAmount(amount, fixedPrice, maxTradeLimit, currencyCode, paymentAccount.getPaymentMethod().getId()); + minAmount = CoinUtil.getRoundedAmount(minAmount, fixedPrice, maxTradeLimit, currencyCode, paymentAccount.getPaymentMethod().getId()); + } + + long priceAsLong = fixedPrice != null ? fixedPrice.getValue() : 0L; double marketPriceMarginParam = useMarketBasedPriceValue ? marketPriceMargin : 0; long amountAsLong = amount != null ? amount.longValueExact() : 0L; long minAmountAsLong = minAmount != null ? minAmount.longValueExact() : 0L; @@ -158,7 +165,6 @@ public class CreateOfferService { BigInteger makerFee = HavenoUtils.getMakerFee(amount); BigInteger buyerSecurityDeposit = getBuyerSecurityDeposit(amount, buyerSecurityDepositAsDouble); BigInteger sellerSecurityDeposit = getSellerSecurityDeposit(amount, sellerSecurityDepositAsDouble); - long maxTradeLimit = offerUtil.getMaxTradeLimit(paymentAccount, currencyCode, direction); long maxTradePeriod = paymentAccount.getMaxTradePeriod(); // reserved for future use cases diff --git a/desktop/src/main/java/haveno/desktop/main/offer/takeoffer/TakeOfferViewModel.java b/desktop/src/main/java/haveno/desktop/main/offer/takeoffer/TakeOfferViewModel.java index 4748d6ae..90607d8a 100644 --- a/desktop/src/main/java/haveno/desktop/main/offer/takeoffer/TakeOfferViewModel.java +++ b/desktop/src/main/java/haveno/desktop/main/offer/takeoffer/TakeOfferViewModel.java @@ -302,10 +302,10 @@ class TakeOfferViewModel extends ActivatableWithDataModel im Price tradePrice = dataModel.tradePrice; long maxTradeLimit = dataModel.getMaxTradeLimit(); if (PaymentMethod.isRoundedForAtmCash(dataModel.getPaymentMethod().getId())) { - BigInteger adjustedAmountForHalCash = CoinUtil.getRoundedAtmCashAmount(dataModel.getAmount().get(), + BigInteger adjustedAmountForAtm = CoinUtil.getRoundedAtmCashAmount(dataModel.getAmount().get(), tradePrice, maxTradeLimit); - dataModel.applyAmount(adjustedAmountForHalCash); + dataModel.applyAmount(adjustedAmountForAtm); amount.set(HavenoUtils.formatXmr(dataModel.getAmount().get())); } else if (dataModel.getOffer().isTraditionalOffer()) { if (!isAmountEqualMinAmount(dataModel.getAmount().get()) && (!isAmountEqualMaxAmount(dataModel.getAmount().get()))) {