diff --git a/common/src/main/proto/pb.proto b/common/src/main/proto/pb.proto index 2d233c8dce..c1574d6b31 100644 --- a/common/src/main/proto/pb.proto +++ b/common/src/main/proto/pb.proto @@ -1354,6 +1354,7 @@ message PreferencesPayload { string take_offer_selected_payment_account_id = 49; double buyer_security_deposit_as_percent = 50; int32 ignore_dust_threshold = 51; + double buyer_security_deposit_as_percent_for_crypto = 52; } /////////////////////////////////////////////////////////////////////////////////////////// diff --git a/core/src/main/java/bisq/core/btc/wallet/Restrictions.java b/core/src/main/java/bisq/core/btc/wallet/Restrictions.java index 3744747bbf..83e51c7274 100644 --- a/core/src/main/java/bisq/core/btc/wallet/Restrictions.java +++ b/core/src/main/java/bisq/core/btc/wallet/Restrictions.java @@ -18,9 +18,13 @@ package bisq.core.btc.wallet; import bisq.core.app.BisqEnvironment; +import bisq.core.payment.PaymentAccount; +import bisq.core.payment.PaymentAccountUtil; import org.bitcoinj.core.Coin; +import javax.annotation.Nullable; + public class Restrictions { private static Coin MIN_TRADE_AMOUNT; private static Coin MIN_BUYER_SECURITY_DEPOSIT; @@ -50,16 +54,25 @@ public class Restrictions { return MIN_TRADE_AMOUNT; } - public static double getDefaultBuyerSecurityDepositAsPercent() { - return 0.1; // 10% of trade amount. + public static double getDefaultBuyerSecurityDepositAsPercent(@Nullable PaymentAccount paymentAccount) { + if (PaymentAccountUtil.isCryptoCurrencyAccount(paymentAccount)) + return 0.02; // 2% of trade amount. + else + return 0.1; // 10% of trade amount. } - public static double getMinBuyerSecurityDepositAsPercent() { - return 0.05; // 5% of trade amount. + public static double getMinBuyerSecurityDepositAsPercent(@Nullable PaymentAccount paymentAccount) { + if (PaymentAccountUtil.isCryptoCurrencyAccount(paymentAccount)) + return 0.005; // 0.5% of trade amount. + else + return 0.05; // 5% of trade amount. } - public static double getMaxBuyerSecurityDepositAsPercent() { - return 0.5; // 50% of trade amount. For a 1 BTC trade it is about 800 USD @ 4000 USD/BTC + public static double getMaxBuyerSecurityDepositAsPercent(@Nullable PaymentAccount paymentAccount) { + if (PaymentAccountUtil.isCryptoCurrencyAccount(paymentAccount)) + return 0.2; // 20% of trade amount. For a 1 BTC trade it is about 800 USD @ 4000 USD/BTC + else + return 0.5; // 50% of trade amount. For a 1 BTC trade it is about 2000 USD @ 4000 USD/BTC } // We use MIN_BUYER_SECURITY_DEPOSIT as well as lower bound in case of small trade amounts. diff --git a/core/src/main/java/bisq/core/offer/OfferUtil.java b/core/src/main/java/bisq/core/offer/OfferUtil.java index 808eeef79f..dbd892a410 100644 --- a/core/src/main/java/bisq/core/offer/OfferUtil.java +++ b/core/src/main/java/bisq/core/offer/OfferUtil.java @@ -353,12 +353,12 @@ public class OfferUtil { Coin makerFeeAsCoin) { checkNotNull(makerFeeAsCoin, "makerFee must not be null"); checkNotNull(p2PService.getAddress(), "Address must not be null"); - checkArgument(buyerSecurityDeposit <= Restrictions.getMaxBuyerSecurityDepositAsPercent(), + checkArgument(buyerSecurityDeposit <= Restrictions.getMaxBuyerSecurityDepositAsPercent(paymentAccount), "securityDeposit must not exceed " + - Restrictions.getMaxBuyerSecurityDepositAsPercent()); - checkArgument(buyerSecurityDeposit >= Restrictions.getMinBuyerSecurityDepositAsPercent(), + Restrictions.getMaxBuyerSecurityDepositAsPercent(paymentAccount)); + checkArgument(buyerSecurityDeposit >= Restrictions.getMinBuyerSecurityDepositAsPercent(paymentAccount), "securityDeposit must not be less than " + - Restrictions.getMinBuyerSecurityDepositAsPercent()); + Restrictions.getMinBuyerSecurityDepositAsPercent(paymentAccount)); checkArgument(!filterManager.isCurrencyBanned(currencyCode), Res.get("offerbook.warning.currencyBanned")); checkArgument(!filterManager.isPaymentMethodBanned(paymentAccount.getPaymentMethod()), diff --git a/core/src/main/java/bisq/core/payment/PaymentAccountUtil.java b/core/src/main/java/bisq/core/payment/PaymentAccountUtil.java index b36b0a26c3..4b0a08d909 100644 --- a/core/src/main/java/bisq/core/payment/PaymentAccountUtil.java +++ b/core/src/main/java/bisq/core/payment/PaymentAccountUtil.java @@ -19,6 +19,7 @@ package bisq.core.payment; import bisq.core.locale.Country; import bisq.core.offer.Offer; +import bisq.core.payment.payload.PaymentMethod; import javafx.collections.FXCollections; import javafx.collections.ObservableList; @@ -114,6 +115,11 @@ public class PaymentAccountUtil { return null; } + public static boolean isCryptoCurrencyAccount(PaymentAccount paymentAccount) { + return (paymentAccount != null && paymentAccount.getPaymentMethod().equals(PaymentMethod.BLOCK_CHAINS) || + paymentAccount != null && paymentAccount.getPaymentMethod().equals(PaymentMethod.BLOCK_CHAINS_INSTANT)); + } + // TODO no code duplication found in UI code (added for API) // That is optional and set to null if not supported (AltCoins,...) /* public static String getCountryCode(PaymentAccount paymentAccount) { diff --git a/core/src/main/java/bisq/core/user/Preferences.java b/core/src/main/java/bisq/core/user/Preferences.java index 1f4b61d509..13c3027fb7 100644 --- a/core/src/main/java/bisq/core/user/Preferences.java +++ b/core/src/main/java/bisq/core/user/Preferences.java @@ -32,6 +32,7 @@ import bisq.core.locale.FiatCurrency; import bisq.core.locale.GlobalSettings; import bisq.core.locale.TradeCurrency; import bisq.core.payment.PaymentAccount; +import bisq.core.payment.PaymentAccountUtil; import bisq.network.p2p.network.BridgeAddressProvider; @@ -493,10 +494,14 @@ public final class Preferences implements PersistedDataHost, BridgeAddressProvid withdrawalTxFeeInBytesProperty.set(withdrawalTxFeeInBytes); } - public void setBuyerSecurityDepositAsPercent(double buyerSecurityDepositAsPercent) { - double max = Restrictions.getMaxBuyerSecurityDepositAsPercent(); - double min = Restrictions.getMinBuyerSecurityDepositAsPercent(); - prefPayload.setBuyerSecurityDepositAsPercent(Math.min(max, Math.max(min, buyerSecurityDepositAsPercent))); + public void setBuyerSecurityDepositAsPercent(double buyerSecurityDepositAsPercent, PaymentAccount paymentAccount) { + double max = Restrictions.getMaxBuyerSecurityDepositAsPercent(paymentAccount); + double min = Restrictions.getMinBuyerSecurityDepositAsPercent(paymentAccount); + + if (PaymentAccountUtil.isCryptoCurrencyAccount(paymentAccount)) + prefPayload.setBuyerSecurityDepositAsPercentForCrypto(Math.min(max, Math.max(min, buyerSecurityDepositAsPercent))); + else + prefPayload.setBuyerSecurityDepositAsPercent(Math.min(max, Math.max(min, buyerSecurityDepositAsPercent))); persist(); } @@ -715,15 +720,16 @@ public final class Preferences implements PersistedDataHost, BridgeAddressProvid return withdrawalTxFeeInBytesProperty; } - public double getBuyerSecurityDepositAsPercent() { - double value = prefPayload.getBuyerSecurityDepositAsPercent(); + public double getBuyerSecurityDepositAsPercent(PaymentAccount paymentAccount) { + double value = PaymentAccountUtil.isCryptoCurrencyAccount(paymentAccount) ? + prefPayload.getBuyerSecurityDepositAsPercentForCrypto() : prefPayload.getBuyerSecurityDepositAsPercent(); - if (value < Restrictions.getMinBuyerSecurityDepositAsPercent()) { - value = Restrictions.getMinBuyerSecurityDepositAsPercent(); - setBuyerSecurityDepositAsPercent(value); + if (value < Restrictions.getMinBuyerSecurityDepositAsPercent(paymentAccount)) { + value = Restrictions.getMinBuyerSecurityDepositAsPercent(paymentAccount); + setBuyerSecurityDepositAsPercent(value, paymentAccount); } - return value == 0 ? Restrictions.getDefaultBuyerSecurityDepositAsPercent() : value; + return value == 0 ? Restrictions.getDefaultBuyerSecurityDepositAsPercent(paymentAccount) : value; } //TODO remove and use isPayFeeInBtc instead diff --git a/core/src/main/java/bisq/core/user/PreferencesPayload.java b/core/src/main/java/bisq/core/user/PreferencesPayload.java index b4fd96aa38..426ee19b6b 100644 --- a/core/src/main/java/bisq/core/user/PreferencesPayload.java +++ b/core/src/main/java/bisq/core/user/PreferencesPayload.java @@ -17,11 +17,11 @@ package bisq.core.user; -import bisq.core.btc.wallet.Restrictions; import bisq.core.locale.Country; import bisq.core.locale.CryptoCurrency; import bisq.core.locale.FiatCurrency; import bisq.core.locale.TradeCurrency; +import bisq.core.payment.CryptoCurrencyAccount; import bisq.core.payment.PaymentAccount; import bisq.core.proto.CoreProtoResolver; @@ -47,6 +47,8 @@ import lombok.extern.slf4j.Slf4j; import javax.annotation.Nullable; +import static bisq.core.btc.wallet.Restrictions.getDefaultBuyerSecurityDepositAsPercent; + @Slf4j @Data @AllArgsConstructor @@ -120,8 +122,9 @@ public final class PreferencesPayload implements PersistableEnvelope { String rpcPw; @Nullable String takeOfferSelectedPaymentAccountId; - private double buyerSecurityDepositAsPercent = Restrictions.getDefaultBuyerSecurityDepositAsPercent(); + private double buyerSecurityDepositAsPercent = getDefaultBuyerSecurityDepositAsPercent(null); private int ignoreDustThreshold = 600; + private double buyerSecurityDepositAsPercentForCrypto = getDefaultBuyerSecurityDepositAsPercent(new CryptoCurrencyAccount()); /////////////////////////////////////////////////////////////////////////////////////////// @@ -178,8 +181,9 @@ public final class PreferencesPayload implements PersistableEnvelope { .setUsePriceNotifications(usePriceNotifications) .setUseStandbyMode(useStandbyMode) .setIsDaoFullNode(isDaoFullNode) - .setBuyerSecurityDepositAsPercent(buyerSecurityDepositAsPercent). - setIgnoreDustThreshold(ignoreDustThreshold); + .setBuyerSecurityDepositAsPercent(buyerSecurityDepositAsPercent) + .setIgnoreDustThreshold(ignoreDustThreshold) + .setBuyerSecurityDepositAsPercentForCrypto(buyerSecurityDepositAsPercentForCrypto); Optional.ofNullable(backupDirectory).ifPresent(builder::setBackupDirectory); Optional.ofNullable(preferredTradeCurrency).ifPresent(e -> builder.setPreferredTradeCurrency((PB.TradeCurrency) e.toProtoMessage())); Optional.ofNullable(offerBookChartScreenCurrencyCode).ifPresent(builder::setOfferBookChartScreenCurrencyCode); @@ -262,6 +266,8 @@ public final class PreferencesPayload implements PersistableEnvelope { proto.getRpcPw().isEmpty() ? null : proto.getRpcPw(), proto.getTakeOfferSelectedPaymentAccountId().isEmpty() ? null : proto.getTakeOfferSelectedPaymentAccountId(), proto.getBuyerSecurityDepositAsPercent(), - proto.getIgnoreDustThreshold()); + proto.getIgnoreDustThreshold(), + proto.getBuyerSecurityDepositAsPercentForCrypto()); + } } diff --git a/desktop/src/main/java/bisq/desktop/main/offer/MutableOfferDataModel.java b/desktop/src/main/java/bisq/desktop/main/offer/MutableOfferDataModel.java index 2d2f02255e..b153cda451 100644 --- a/desktop/src/main/java/bisq/desktop/main/offer/MutableOfferDataModel.java +++ b/desktop/src/main/java/bisq/desktop/main/offer/MutableOfferDataModel.java @@ -180,7 +180,7 @@ public abstract class MutableOfferDataModel extends OfferDataModel implements Bs addressEntry = btcWalletService.getOrCreateAddressEntry(offerId, AddressEntry.Context.OFFER_FUNDING); useMarketBasedPrice.set(preferences.isUsePercentageBasedPrice()); - buyerSecurityDeposit.set(preferences.getBuyerSecurityDepositAsPercent()); + buyerSecurityDeposit.set(preferences.getBuyerSecurityDepositAsPercent(null)); sellerSecurityDeposit.set(Restrictions.getSellerSecurityDepositAsPercent()); btcBalanceListener = new BalanceListener(getAddressEntry().getAddress()) { @@ -436,6 +436,8 @@ public abstract class MutableOfferDataModel extends OfferDataModel implements Bs setTradeCurrencyFromPaymentAccount(paymentAccount); + buyerSecurityDeposit.set(preferences.getBuyerSecurityDepositAsPercent(getPaymentAccount())); + long myLimit = accountAgeWitnessService.getMyTradeLimit(paymentAccount, tradeCurrencyCode.get()); if (amount.get() != null) this.amount.set(Coin.valueOf(Math.min(amount.get().value, myLimit))); @@ -710,7 +712,7 @@ public abstract class MutableOfferDataModel extends OfferDataModel implements Bs void setBuyerSecurityDeposit(double value) { this.buyerSecurityDeposit.set(value); - preferences.setBuyerSecurityDepositAsPercent(value); + preferences.setBuyerSecurityDepositAsPercent(value, getPaymentAccount()); } protected boolean isUseMarketBasedPriceValue() { 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 19185d1da5..ce3ebc972b 100644 --- a/desktop/src/main/java/bisq/desktop/main/offer/MutableOfferViewModel.java +++ b/desktop/src/main/java/bisq/desktop/main/offer/MutableOfferViewModel.java @@ -599,6 +599,7 @@ public abstract class MutableOfferViewModel ext amountDescription = Res.get("createOffer.amountPriceBox.amountDescription", isBuy ? Res.get("shared.buy") : Res.get("shared.sell")); + securityDepositValidator.setPaymentAccount(dataModel.paymentAccount); buyerSecurityDeposit.set(btcFormatter.formatToPercent(dataModel.getBuyerSecurityDeposit().get())); buyerSecurityDepositLabel.set(getSecurityDepositLabel()); @@ -663,6 +664,8 @@ public abstract class MutableOfferViewModel ext btcValidator.setMaxValue(dataModel.paymentAccount.getPaymentMethod().getMaxTradeLimitAsCoin(dataModel.getTradeCurrencyCode().get())); btcValidator.setMaxTradeLimit(Coin.valueOf(dataModel.getMaxTradeLimit())); + + securityDepositValidator.setPaymentAccount(paymentAccount); } public void onCurrencySelected(TradeCurrency tradeCurrency) { @@ -853,7 +856,7 @@ public abstract class MutableOfferViewModel ext InputValidator.ValidationResult result = securityDepositValidator.validate(buyerSecurityDeposit.get()); buyerSecurityDepositValidationResult.set(result); if (result.isValid) { - double defaultSecurityDeposit = Restrictions.getDefaultBuyerSecurityDepositAsPercent(); + double defaultSecurityDeposit = Restrictions.getDefaultBuyerSecurityDepositAsPercent(getPaymentAccount()); String key = "buyerSecurityDepositIsLowerAsDefault"; double depositAsDouble = btcFormatter.parsePercentStringToDouble(buyerSecurityDeposit.get()); if (preferences.showAgain(key) && depositAsDouble < defaultSecurityDeposit) { @@ -1121,7 +1124,7 @@ public abstract class MutableOfferViewModel ext if (buyerSecurityDeposit.get() != null && !buyerSecurityDeposit.get().isEmpty()) { dataModel.setBuyerSecurityDeposit(btcFormatter.parsePercentStringToDouble(buyerSecurityDeposit.get())); } else { - dataModel.setBuyerSecurityDeposit(Restrictions.getDefaultBuyerSecurityDepositAsPercent()); + dataModel.setBuyerSecurityDeposit(Restrictions.getDefaultBuyerSecurityDepositAsPercent(getPaymentAccount())); } } diff --git a/desktop/src/main/java/bisq/desktop/util/validation/SecurityDepositValidator.java b/desktop/src/main/java/bisq/desktop/util/validation/SecurityDepositValidator.java index 8dd479f921..04ecfe59f8 100644 --- a/desktop/src/main/java/bisq/desktop/util/validation/SecurityDepositValidator.java +++ b/desktop/src/main/java/bisq/desktop/util/validation/SecurityDepositValidator.java @@ -19,6 +19,7 @@ package bisq.desktop.util.validation; import bisq.core.btc.wallet.Restrictions; import bisq.core.locale.Res; +import bisq.core.payment.PaymentAccount; import bisq.core.util.BSFormatter; import javax.inject.Inject; @@ -26,12 +27,16 @@ import javax.inject.Inject; public class SecurityDepositValidator extends NumberValidator { private final BSFormatter formatter; + private PaymentAccount paymentAccount; @Inject public SecurityDepositValidator(BSFormatter formatter) { this.formatter = formatter; } + public void setPaymentAccount(PaymentAccount paymentAccount) { + this.paymentAccount = paymentAccount; + } @Override public ValidationResult validate(String input) { @@ -54,7 +59,7 @@ public class SecurityDepositValidator extends NumberValidator { private ValidationResult validateIfNotTooLowPercentageValue(String input) { try { double percentage = formatter.parsePercentStringToDouble(input); - double minPercentage = Restrictions.getMinBuyerSecurityDepositAsPercent(); + double minPercentage = Restrictions.getMinBuyerSecurityDepositAsPercent(paymentAccount); if (percentage < minPercentage) return new ValidationResult(false, Res.get("validation.inputTooSmall", formatter.formatToPercentWithSymbol(minPercentage))); @@ -68,7 +73,7 @@ public class SecurityDepositValidator extends NumberValidator { private ValidationResult validateIfNotTooHighPercentageValue(String input) { try { double percentage = formatter.parsePercentStringToDouble(input); - double maxPercentage = Restrictions.getMaxBuyerSecurityDepositAsPercent(); + double maxPercentage = Restrictions.getMaxBuyerSecurityDepositAsPercent(paymentAccount); if (percentage > maxPercentage) return new ValidationResult(false, Res.get("validation.inputTooLarge", formatter.formatToPercentWithSymbol(maxPercentage))); diff --git a/desktop/src/test/java/bisq/desktop/main/offer/createoffer/CreateOfferDataModelTest.java b/desktop/src/test/java/bisq/desktop/main/offer/createoffer/CreateOfferDataModelTest.java index 1d3d931fa3..dbc6249aaa 100644 --- a/desktop/src/test/java/bisq/desktop/main/offer/createoffer/CreateOfferDataModelTest.java +++ b/desktop/src/test/java/bisq/desktop/main/offer/createoffer/CreateOfferDataModelTest.java @@ -66,7 +66,7 @@ public class CreateOfferDataModelTest { when(btcWalletService.getOrCreateAddressEntry(anyString(), any())).thenReturn(addressEntry); when(preferences.isUsePercentageBasedPrice()).thenReturn(true); - when(preferences.getBuyerSecurityDepositAsPercent()).thenReturn(0.01); + when(preferences.getBuyerSecurityDepositAsPercent(null)).thenReturn(0.01); model = new CreateOfferDataModel(null, btcWalletService, null, preferences, user, null,