From b8f8fbff20ffcdfd6551a8c4efeaa81953bb29a2 Mon Sep 17 00:00:00 2001 From: Manfred Karrer Date: Mon, 4 Mar 2019 23:35:35 -0500 Subject: [PATCH 1/6] Use percentage based value for security deposits Use percentage based value of trade amount for buyer and seller security deposit. To ensure that the BTC value is not getting too low, we apply a min. value for both. --- common/src/main/proto/pb.proto | 3 +- .../bisq/core/btc/wallet/Restrictions.java | 34 +++++---- .../main/java/bisq/core/offer/OfferUtil.java | 14 ++-- core/src/main/java/bisq/core/trade/Trade.java | 4 +- .../main/java/bisq/core/user/Preferences.java | 21 +++--- .../bisq/core/user/PreferencesPayload.java | 12 +++- .../main/java/bisq/core/util/CoinUtil.java | 20 ++++++ .../resources/i18n/displayStrings.properties | 4 +- .../main/offer/MutableOfferDataModel.java | 69 +++++++++++++------ .../desktop/main/offer/MutableOfferView.java | 6 +- .../main/offer/MutableOfferViewModel.java | 37 +++++----- .../editoffer/EditOfferDataModel.java | 5 +- .../validation/SecurityDepositValidator.java | 40 +++++++---- .../createoffer/CreateOfferDataModelTest.java | 2 +- 14 files changed, 170 insertions(+), 101 deletions(-) diff --git a/common/src/main/proto/pb.proto b/common/src/main/proto/pb.proto index 1aa7b5dfb9c..6f6adf5c0d8 100644 --- a/common/src/main/proto/pb.proto +++ b/common/src/main/proto/pb.proto @@ -1278,7 +1278,7 @@ message PreferencesPayload { string bitcoin_nodes = 27; repeated string ignore_traders_list = 28; string directory_chooser_path = 29; - int64 buyer_security_deposit_as_long = 30; + int64 buyer_security_deposit_as_long = 30; // Deprectated: Superseded by buyerSecurityDepositAsPercent bool use_animations = 31; PaymentAccount selectedPayment_account_for_createOffer = 32; bool pay_fee_in_Btc = 33; @@ -1298,6 +1298,7 @@ message PreferencesPayload { string rpc_user = 47; string rpc_pw = 48; string take_offer_selected_payment_account_id = 49; + double buyer_security_deposit_as_percent = 50; } /////////////////////////////////////////////////////////////////////////////////////////// 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 6bb6590bed9..cd4ea3f022a 100644 --- a/core/src/main/java/bisq/core/btc/wallet/Restrictions.java +++ b/core/src/main/java/bisq/core/btc/wallet/Restrictions.java @@ -23,9 +23,7 @@ public class Restrictions { private static Coin MIN_TRADE_AMOUNT; - private static Coin MAX_BUYER_SECURITY_DEPOSIT; private static Coin MIN_BUYER_SECURITY_DEPOSIT; - private static Coin DEFAULT_BUYER_SECURITY_DEPOSIT; // For the seller we use a fixed one as there is no way the seller can cancel the trade // To make it editable would just increase complexity. private static Coin SELLER_SECURITY_DEPOSIT; @@ -52,28 +50,34 @@ public static Coin getMinTradeAmount() { return MIN_TRADE_AMOUNT; } - // Can be reduced but not increased. Otherwise would break existing offers! - public static Coin getMaxBuyerSecurityDeposit() { - if (MAX_BUYER_SECURITY_DEPOSIT == null) - MAX_BUYER_SECURITY_DEPOSIT = Coin.valueOf(5_000_000); // 1000 USD @ 20000 USD/BTC - return MAX_BUYER_SECURITY_DEPOSIT; + public static double getDefaultBuyerSecurityDepositAsPercent() { + return 0.01; // 1% of trade amount } - public static Coin getMinBuyerSecurityDeposit() { + public static double getMinBuyerSecurityDepositAsPercent() { + return 0.0005; // 0.05% of trade amount + } + + public static double getMaxBuyerSecurityDepositAsPercent() { + return 0.1; // 10% of trade amount + } + + // We use MIN_BUYER_SECURITY_DEPOSIT as well as lower bound in case of small trade amounts. + // So 0.0005 BTC is the min. buyer security deposit even with amount of 0.0001 BTC and 0.05% percentage value. + public static Coin getMinBuyerSecurityDepositAsCoin() { if (MIN_BUYER_SECURITY_DEPOSIT == null) - MIN_BUYER_SECURITY_DEPOSIT = Coin.valueOf(50_000); // 10 USD @ 20000 USD/BTC + MIN_BUYER_SECURITY_DEPOSIT = Coin.parseCoin("0.0005"); // 0.0005 BTC of a 1 BTC trade (0.05%) return MIN_BUYER_SECURITY_DEPOSIT; } - public static Coin getDefaultBuyerSecurityDeposit() { - if (DEFAULT_BUYER_SECURITY_DEPOSIT == null) - DEFAULT_BUYER_SECURITY_DEPOSIT = Coin.valueOf(1_000_000); // 200 EUR @ 20000 USD/BTC - return DEFAULT_BUYER_SECURITY_DEPOSIT; + + public static double getSellerSecurityDepositAsPercent() { + return 0.003; } - public static Coin getSellerSecurityDeposit() { + public static Coin getMinSellerSecurityDepositAsCoin() { if (SELLER_SECURITY_DEPOSIT == null) - SELLER_SECURITY_DEPOSIT = Coin.valueOf(300_000); // 60 USD @ 20000 USD/BTC + SELLER_SECURITY_DEPOSIT = Coin.parseCoin("0.0005"); // 0.0005 BTC of a 1 BTC trade (0.05%) return SELLER_SECURITY_DEPOSIT; } } diff --git a/core/src/main/java/bisq/core/offer/OfferUtil.java b/core/src/main/java/bisq/core/offer/OfferUtil.java index dbda05563e6..808eeef79fc 100644 --- a/core/src/main/java/bisq/core/offer/OfferUtil.java +++ b/core/src/main/java/bisq/core/offer/OfferUtil.java @@ -347,18 +347,18 @@ public static Map getExtraDataMap(AccountAgeWitnessService accou public static void validateOfferData(FilterManager filterManager, P2PService p2PService, - Coin buyerSecurityDepositAsCoin, + double buyerSecurityDeposit, PaymentAccount paymentAccount, String currencyCode, Coin makerFeeAsCoin) { checkNotNull(makerFeeAsCoin, "makerFee must not be null"); checkNotNull(p2PService.getAddress(), "Address must not be null"); - checkArgument(buyerSecurityDepositAsCoin.compareTo(Restrictions.getMaxBuyerSecurityDeposit()) <= 0, - "securityDeposit must be not exceed " + - Restrictions.getMaxBuyerSecurityDeposit().toFriendlyString()); - checkArgument(buyerSecurityDepositAsCoin.compareTo(Restrictions.getMinBuyerSecurityDeposit()) >= 0, - "securityDeposit must be not be less than " + - Restrictions.getMinBuyerSecurityDeposit().toFriendlyString()); + checkArgument(buyerSecurityDeposit <= Restrictions.getMaxBuyerSecurityDepositAsPercent(), + "securityDeposit must not exceed " + + Restrictions.getMaxBuyerSecurityDepositAsPercent()); + checkArgument(buyerSecurityDeposit >= Restrictions.getMinBuyerSecurityDepositAsPercent(), + "securityDeposit must not be less than " + + Restrictions.getMinBuyerSecurityDepositAsPercent()); checkArgument(!filterManager.isCurrencyBanned(currencyCode), Res.get("offerbook.warning.currencyBanned")); checkArgument(!filterManager.isPaymentMethodBanned(paymentAccount.getPaymentMethod()), diff --git a/core/src/main/java/bisq/core/trade/Trade.java b/core/src/main/java/bisq/core/trade/Trade.java index ef9e7438850..3186394301a 100644 --- a/core/src/main/java/bisq/core/trade/Trade.java +++ b/core/src/main/java/bisq/core/trade/Trade.java @@ -619,9 +619,9 @@ public void onComplete() { /////////////////////////////////////////////////////////////////////////////////////////// public void setState(State state) { - log.info("Set new state at {} (id={}): {}", this.getClass().getSimpleName(), getShortId(), state); + log.debug("Set new state at {} (id={}): {}", this.getClass().getSimpleName(), getShortId(), state); if (state.getPhase().ordinal() < this.state.getPhase().ordinal()) { - final String message = "We got a state change to a previous phase.\n" + + String message = "We got a state change to a previous phase.\n" + "Old state is: " + this.state + ". New state is: " + state; log.warn(message); } diff --git a/core/src/main/java/bisq/core/user/Preferences.java b/core/src/main/java/bisq/core/user/Preferences.java index 639921ccafe..81871642c4f 100644 --- a/core/src/main/java/bisq/core/user/Preferences.java +++ b/core/src/main/java/bisq/core/user/Preferences.java @@ -39,8 +39,6 @@ import bisq.common.storage.Storage; import bisq.common.util.Utilities; -import org.bitcoinj.core.Coin; - import javax.inject.Inject; import javax.inject.Named; @@ -487,10 +485,10 @@ public void setWithdrawalTxFeeInBytes(long withdrawalTxFeeInBytes) { withdrawalTxFeeInBytesProperty.set(withdrawalTxFeeInBytes); } - public void setBuyerSecurityDepositAsLong(long buyerSecurityDepositAsLong) { - prefPayload.setBuyerSecurityDepositAsLong(Math.min(Restrictions.getMaxBuyerSecurityDeposit().value, - Math.max(Restrictions.getMinBuyerSecurityDeposit().value, - buyerSecurityDepositAsLong))); + public void setBuyerSecurityDepositAsPercent(double buyerSecurityDepositAsPercent) { + double max = Restrictions.getMaxBuyerSecurityDepositAsPercent(); + double min = Restrictions.getMinBuyerSecurityDepositAsPercent(); + prefPayload.setBuyerSecurityDepositAsPercent(Math.min(max, Math.max(min, buyerSecurityDepositAsPercent))); persist(); } @@ -696,8 +694,9 @@ public LongProperty withdrawalTxFeeInBytesProperty() { return withdrawalTxFeeInBytesProperty; } - public Coin getBuyerSecurityDepositAsCoin() { - return Coin.valueOf(prefPayload.getBuyerSecurityDepositAsLong()); + public double getBuyerSecurityDepositAsPercent() { + double value = prefPayload.getBuyerSecurityDepositAsPercent(); + return value == 0 ? Restrictions.getDefaultBuyerSecurityDepositAsPercent() : value; } //TODO remove and use isPayFeeInBtc instead @@ -775,8 +774,6 @@ private interface ExcludesDelegateMethods { void setWithdrawalTxFeeInBytes(long withdrawalTxFeeInBytes); - void setBuyerSecurityDepositAsLong(long buyerSecurityDepositAsLong); - void setSelectedPaymentAccountForCreateOffer(@Nullable PaymentAccount paymentAccount); void setBsqBlockChainExplorer(BlockChainExplorer bsqBlockChainExplorer); @@ -832,5 +829,9 @@ private interface ExcludesDelegateMethods { void setRpcPw(String value); void setTakeOfferSelectedPaymentAccountId(String value); + + void setBuyerSecurityDepositAsPercent(double buyerSecurityDepositAsPercent); + + double getBuyerSecurityDepositAsPercent(); } } diff --git a/core/src/main/java/bisq/core/user/PreferencesPayload.java b/core/src/main/java/bisq/core/user/PreferencesPayload.java index 662126bfa34..9c62b20c815 100644 --- a/core/src/main/java/bisq/core/user/PreferencesPayload.java +++ b/core/src/main/java/bisq/core/user/PreferencesPayload.java @@ -89,7 +89,10 @@ public final class PreferencesPayload implements PersistableEnvelope { private String bitcoinNodes = ""; private List ignoreTradersList = new ArrayList<>(); private String directoryChooserPath; - private long buyerSecurityDepositAsLong = Restrictions.getDefaultBuyerSecurityDeposit().value; + + @Deprecated // Superseded by buyerSecurityDepositAsPercent + private long buyerSecurityDepositAsLong; + private boolean useAnimations; @Nullable private PaymentAccount selectedPaymentAccountForCreateOffer; @@ -117,6 +120,7 @@ public final class PreferencesPayload implements PersistableEnvelope { String rpcPw; @Nullable String takeOfferSelectedPaymentAccountId; + private double buyerSecurityDepositAsPercent = Restrictions.getDefaultBuyerSecurityDepositAsPercent(); /////////////////////////////////////////////////////////////////////////////////////////// @@ -172,7 +176,8 @@ public Message toProtoMessage() { .setUseMarketNotifications(useMarketNotifications) .setUsePriceNotifications(usePriceNotifications) .setUseStandbyMode(useStandbyMode) - .setIsDaoFullNode(isDaoFullNode); + .setIsDaoFullNode(isDaoFullNode) + .setBuyerSecurityDepositAsPercent(buyerSecurityDepositAsPercent); Optional.ofNullable(backupDirectory).ifPresent(builder::setBackupDirectory); Optional.ofNullable(preferredTradeCurrency).ifPresent(e -> builder.setPreferredTradeCurrency((PB.TradeCurrency) e.toProtoMessage())); Optional.ofNullable(offerBookChartScreenCurrencyCode).ifPresent(builder::setOfferBookChartScreenCurrencyCode); @@ -253,6 +258,7 @@ public static PersistableEnvelope fromProto(PB.PreferencesPayload proto, CorePro proto.getIsDaoFullNode(), proto.getRpcUser().isEmpty() ? null : proto.getRpcUser(), proto.getRpcPw().isEmpty() ? null : proto.getRpcPw(), - proto.getTakeOfferSelectedPaymentAccountId().isEmpty() ? null : proto.getTakeOfferSelectedPaymentAccountId()); + proto.getTakeOfferSelectedPaymentAccountId().isEmpty() ? null : proto.getTakeOfferSelectedPaymentAccountId(), + proto.getBuyerSecurityDepositAsPercent()); } } diff --git a/core/src/main/java/bisq/core/util/CoinUtil.java b/core/src/main/java/bisq/core/util/CoinUtil.java index 26c8982643a..b1695c072e7 100644 --- a/core/src/main/java/bisq/core/util/CoinUtil.java +++ b/core/src/main/java/bisq/core/util/CoinUtil.java @@ -41,4 +41,24 @@ public static Coin maxCoin(Coin a, Coin b) { public static double getFeePerByte(Coin miningFee, int txSize) { return MathUtils.roundDouble(((double) miningFee.value / (double) txSize), 2); } + + /** + * @param value Btc amount to be converted to percent value. E.g. 0.01 BTC is 1% (of 1 BTC) + * @return The percentage value as double (e.g. 1% is 0.01) + */ + public static double getAsPercentPerBtc(Coin value) { + double asDouble = (double) value.value; + double btcAsDouble = (double) Coin.COIN.value; + return MathUtils.roundDouble(asDouble / btcAsDouble, 4); + } + + /** + * @param percent The percentage value as double (e.g. 1% is 0.01) + * @param amount The amount as Coin for the percentage calculation + * @return The percentage as Coin (e.g. 1% of 1 BTC is 0.01 BTC) + */ + public static Coin getPercentOfAmountAsCoin(double percent, Coin amount) { + double amountAsDouble = (double) amount.value; + return Coin.valueOf(Math.round(percent * amountAsDouble)); + } } diff --git a/core/src/main/resources/i18n/displayStrings.properties b/core/src/main/resources/i18n/displayStrings.properties index b8a6919d832..dd354e45226 100644 --- a/core/src/main/resources/i18n/displayStrings.properties +++ b/core/src/main/resources/i18n/displayStrings.properties @@ -368,7 +368,7 @@ createOffer.amountPriceBox.amountDescription=Amount of BTC to {0} createOffer.amountPriceBox.buy.volumeDescription=Amount in {0} to spend createOffer.amountPriceBox.sell.volumeDescription=Amount in {0} to receive createOffer.amountPriceBox.minAmountDescription=Minimum amount of BTC -createOffer.securityDeposit.prompt=Security deposit in BTC +createOffer.securityDeposit.prompt=Security deposit createOffer.fundsBox.title=Fund your offer createOffer.fundsBox.offerFee=Trade fee createOffer.fundsBox.networkFee=Mining fee @@ -416,7 +416,7 @@ createOffer.priceOutSideOfDeviation=The price you have entered is outside the ma createOffer.changePrice=Change price createOffer.tac=With publishing this offer I agree to trade with any trader who fulfills the conditions as defined in this screen. createOffer.currencyForFee=Trade fee -createOffer.setDeposit=Set buyer's security deposit +createOffer.setDeposit=Set buyer's security deposit (%) #################################################################### 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 c941c0e4d78..c591df7705c 100644 --- a/desktop/src/main/java/bisq/desktop/main/offer/MutableOfferDataModel.java +++ b/desktop/src/main/java/bisq/desktop/main/offer/MutableOfferDataModel.java @@ -46,6 +46,7 @@ import bisq.core.user.Preferences; import bisq.core.user.User; import bisq.core.util.BSFormatter; +import bisq.core.util.CoinUtil; import bisq.network.p2p.P2PService; @@ -62,11 +63,14 @@ import com.google.common.collect.Lists; import javafx.beans.property.BooleanProperty; +import javafx.beans.property.DoubleProperty; import javafx.beans.property.ObjectProperty; import javafx.beans.property.ReadOnlyBooleanProperty; +import javafx.beans.property.ReadOnlyDoubleProperty; import javafx.beans.property.ReadOnlyObjectProperty; import javafx.beans.property.ReadOnlyStringProperty; import javafx.beans.property.SimpleBooleanProperty; +import javafx.beans.property.SimpleDoubleProperty; import javafx.beans.property.SimpleObjectProperty; import javafx.beans.property.SimpleStringProperty; import javafx.beans.property.StringProperty; @@ -105,8 +109,6 @@ public abstract class MutableOfferDataModel extends OfferDataModel implements Bs private final BalanceListener btcBalanceListener; private final SetChangeListener paymentAccountsChangeListener; - private final Coin sellerSecurityDeposit; - protected OfferPayload.Direction direction; protected TradeCurrency tradeCurrency; protected final StringProperty tradeCurrencyCode = new SimpleStringProperty(); @@ -119,7 +121,10 @@ public abstract class MutableOfferDataModel extends OfferDataModel implements Bs protected final ObjectProperty minAmount = new SimpleObjectProperty<>(); protected final ObjectProperty price = new SimpleObjectProperty<>(); protected final ObjectProperty volume = new SimpleObjectProperty<>(); - protected final ObjectProperty buyerSecurityDeposit = new SimpleObjectProperty<>(); + + // Percentage value of buyer security deposit. E.g. 0.01 means 1% of trade amount + protected final DoubleProperty buyerSecurityDeposit = new SimpleDoubleProperty(); + protected final DoubleProperty sellerSecurityDeposit = new SimpleDoubleProperty(); protected final ObservableList paymentAccounts = FXCollections.observableArrayList(); @@ -174,8 +179,8 @@ public MutableOfferDataModel(OpenOfferManager openOfferManager, addressEntry = btcWalletService.getOrCreateAddressEntry(offerId, AddressEntry.Context.OFFER_FUNDING); useMarketBasedPrice.set(preferences.isUsePercentageBasedPrice()); - buyerSecurityDeposit.set(preferences.getBuyerSecurityDepositAsCoin()); - sellerSecurityDeposit = Restrictions.getSellerSecurityDeposit(); + buyerSecurityDeposit.set(preferences.getBuyerSecurityDepositAsPercent()); + sellerSecurityDeposit.set(Restrictions.getSellerSecurityDepositAsPercent()); btcBalanceListener = new BalanceListener(getAddressEntry().getAddress()) { @Override @@ -230,13 +235,13 @@ private void addListeners() { user.getPaymentAccountsAsObservable().addListener(paymentAccountsChangeListener); } - private void removeListeners() { btcWalletService.removeBalanceListener(btcBalanceListener); bsqWalletService.removeBsqBalanceListener(this); user.getPaymentAccountsAsObservable().removeListener(paymentAccountsChangeListener); } + /////////////////////////////////////////////////////////////////////////////////////////// // API /////////////////////////////////////////////////////////////////////////////////////////// @@ -316,8 +321,8 @@ Offer createAndGetOffer() { String counterCurrencyCode = isCryptoCurrency ? Res.getBaseCurrencyCode() : currencyCode; double marketPriceMarginParam = useMarketBasedPriceValue ? marketPriceMargin : 0; - long amount = this.amount.get() != null ? this.amount.get().getValue() : 0L; - long minAmount = this.minAmount.get() != null ? this.minAmount.get().getValue() : 0L; + long amountAsLong = this.amount.get() != null ? this.amount.get().getValue() : 0L; + long minAmountAsLong = this.minAmount.get() != null ? this.minAmount.get().getValue() : 0L; List acceptedCountryCodes = PaymentAccountUtil.getAcceptedCountryCodes(paymentAccount); List acceptedBanks = PaymentAccountUtil.getAcceptedBanks(paymentAccount); @@ -335,7 +340,7 @@ Offer createAndGetOffer() { long lowerClosePrice = 0; long upperClosePrice = 0; String hashOfChallenge = null; - Coin buyerSecurityDepositAsCoin = buyerSecurityDeposit.get(); + Coin makerFeeAsCoin = getMakerFee(); Map extraDataMap = OfferUtil.getExtraDataMap(accountAgeWitnessService, @@ -345,7 +350,7 @@ Offer createAndGetOffer() { OfferUtil.validateOfferData(filterManager, p2PService, - buyerSecurityDepositAsCoin, + buyerSecurityDeposit.get(), paymentAccount, currencyCode, makerFeeAsCoin); @@ -358,8 +363,8 @@ Offer createAndGetOffer() { priceAsLong, marketPriceMarginParam, useMarketBasedPriceValue, - amount, - minAmount, + amountAsLong, + minAmountAsLong, baseCurrencyCode, counterCurrencyCode, Lists.newArrayList(user.getAcceptedArbitratorAddresses()), @@ -376,8 +381,8 @@ Offer createAndGetOffer() { txFeeFromFeeService.value, makerFeeAsCoin.value, isCurrencyForMakerFeeBtc(), - buyerSecurityDepositAsCoin.value, - sellerSecurityDeposit.value, + getBuyerSecurityDepositAsCoin().value, + getSellerSecurityDepositAsCoin().value, maxTradeLimit, maxTradePeriod, useAutoClose, @@ -642,7 +647,7 @@ void calculateTotalToPay() { } Coin getSecurityDeposit() { - return isBuyOffer() ? buyerSecurityDeposit.get() : sellerSecurityDeposit; + return isBuyOffer() ? getBuyerSecurityDepositAsCoin() : getSellerSecurityDepositAsCoin(); } public boolean isBuyOffer() { @@ -677,9 +682,9 @@ protected void setVolume(Volume volume) { this.volume.set(volume); } - void setBuyerSecurityDeposit(Coin buyerSecurityDeposit) { - this.buyerSecurityDeposit.set(buyerSecurityDeposit); - preferences.setBuyerSecurityDepositAsLong(buyerSecurityDeposit.value); + void setBuyerSecurityDeposit(double value) { + this.buyerSecurityDeposit.set(value); + preferences.setBuyerSecurityDepositAsPercent(value); } protected boolean isUseMarketBasedPriceValue() { @@ -718,12 +723,34 @@ ReadOnlyBooleanProperty getUseMarketBasedPrice() { return useMarketBasedPrice; } - ReadOnlyObjectProperty getBuyerSecurityDeposit() { + ReadOnlyDoubleProperty getBuyerSecurityDeposit() { return buyerSecurityDeposit; } - Coin getSellerSecurityDeposit() { - return sellerSecurityDeposit; + private Coin getBuyerSecurityDepositAsCoin() { + Coin percentOfAmountAsCoin = CoinUtil.getPercentOfAmountAsCoin(buyerSecurityDeposit.get(), amount.get()); + return getBoundedBuyerSecurityDepositAsCoin(percentOfAmountAsCoin); + } + + Coin getSellerSecurityDepositAsCoin() { + Coin amountAsCoin = this.amount.get(); + if (amountAsCoin == null) + amountAsCoin = Coin.ZERO; + + Coin percentOfAmountAsCoin = CoinUtil.getPercentOfAmountAsCoin(sellerSecurityDeposit.get(), amountAsCoin); + return getBoundedSellerSecurityDepositAsCoin(percentOfAmountAsCoin); + } + + private Coin getBoundedBuyerSecurityDepositAsCoin(Coin value) { + // We need to ensure that for small amount values we don't get a too low BTC amount. We limit it with using the + // MinBuyerSecurityDepositAsCoin from Restrictions. + return Coin.valueOf(Math.max(Restrictions.getMinBuyerSecurityDepositAsCoin().value, value.value)); + } + + private Coin getBoundedSellerSecurityDepositAsCoin(Coin value) { + // We need to ensure that for small amount values we don't get a too low BTC amount. We limit it with using the + // MinSellerSecurityDepositAsCoin from Restrictions. + return Coin.valueOf(Math.max(Restrictions.getMinSellerSecurityDepositAsCoin().value, value.value)); } ReadOnlyObjectProperty totalToPayAsCoinProperty() { diff --git a/desktop/src/main/java/bisq/desktop/main/offer/MutableOfferView.java b/desktop/src/main/java/bisq/desktop/main/offer/MutableOfferView.java index e3791252e72..3daef035e71 100644 --- a/desktop/src/main/java/bisq/desktop/main/offer/MutableOfferView.java +++ b/desktop/src/main/java/bisq/desktop/main/offer/MutableOfferView.java @@ -1102,13 +1102,15 @@ private VBox getBuyerSecurityDepositBox() { Tuple3 tuple = getEditableValueBox( Res.get("createOffer.securityDeposit.prompt")); buyerSecurityDepositInputTextField = tuple.second; - Label buyerSecurityDepositBtcLabel = tuple.third; + Label buyerSecurityDepositPercentageLabel = tuple.third; + // getEditableValueBox delivers BTC, so we overwrite it with % + buyerSecurityDepositPercentageLabel.setText("%"); VBox depositBox = getTradeInputBox(tuple.first, Res.get("createOffer.setDeposit")).second; depositBox.setMaxWidth(310); editOfferElements.add(buyerSecurityDepositInputTextField); - editOfferElements.add(buyerSecurityDepositBtcLabel); + editOfferElements.add(buyerSecurityDepositPercentageLabel); return depositBox; } 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 075350fa406..96ba7d8f649 100644 --- a/desktop/src/main/java/bisq/desktop/main/offer/MutableOfferViewModel.java +++ b/desktop/src/main/java/bisq/desktop/main/offer/MutableOfferViewModel.java @@ -105,7 +105,6 @@ public abstract class MutableOfferViewModel ext public final StringProperty amount = new SimpleStringProperty(); public final StringProperty minAmount = new SimpleStringProperty(); final StringProperty buyerSecurityDeposit = new SimpleStringProperty(); - final String sellerSecurityDeposit; // Price in the viewModel is always dependent on fiat/altcoin: Fiat Fiat/BTC, for altcoins we use inverted price. // The domain (dataModel) uses always the same price model (otherCurrencyBTC) @@ -158,7 +157,7 @@ public abstract class MutableOfferViewModel ext private ChangeListener minAmountAsCoinListener; private ChangeListener priceListener; private ChangeListener volumeListener; - private ChangeListener securityDepositAsCoinListener; + private ChangeListener securityDepositAsDoubleListener; private ChangeListener isWalletFundedListener; //private ChangeListener feeFromFundingTxListener; @@ -209,7 +208,6 @@ public MutableOfferViewModel(M dataModel, this.bsqFormatter = bsqFormatter; paymentLabel = Res.get("createOffer.fundsBox.paymentLabel", dataModel.shortOfferId); - sellerSecurityDeposit = btcFormatter.formatCoin(dataModel.getSellerSecurityDeposit()); if (dataModel.getAddressEntry() != null) { addressAsString = dataModel.getAddressEntry().getAddressString(); @@ -223,7 +221,7 @@ public void activate() { if (DevEnv.isDevMode()) { UserThread.runAfter(() -> { amount.set("1"); - price.set("0.0002"); + price.set("0.03"); minAmount.set(amount.get()); onFocusOutPriceAsPercentageTextField(true, false); applyMakerFee(); @@ -456,9 +454,9 @@ private void createListeners() { applyMakerFee(); }; - securityDepositAsCoinListener = (ov, oldValue, newValue) -> { + securityDepositAsDoubleListener = (ov, oldValue, newValue) -> { if (newValue != null) - buyerSecurityDeposit.set(btcFormatter.formatCoin(newValue)); + buyerSecurityDeposit.set(btcFormatter.formatToPercent((double) newValue)); else buyerSecurityDeposit.set(""); }; @@ -543,7 +541,7 @@ private void addListeners() { dataModel.getMinAmount().addListener(minAmountAsCoinListener); dataModel.getPrice().addListener(priceListener); dataModel.getVolume().addListener(volumeListener); - dataModel.getBuyerSecurityDeposit().addListener(securityDepositAsCoinListener); + dataModel.getBuyerSecurityDeposit().addListener(securityDepositAsDoubleListener); // dataModel.feeFromFundingTxProperty.addListener(feeFromFundingTxListener); dataModel.getIsBtcWalletFunded().addListener(isWalletFundedListener); @@ -565,7 +563,7 @@ private void removeListeners() { dataModel.getMinAmount().removeListener(minAmountAsCoinListener); dataModel.getPrice().removeListener(priceListener); dataModel.getVolume().removeListener(volumeListener); - dataModel.getBuyerSecurityDeposit().removeListener(securityDepositAsCoinListener); + dataModel.getBuyerSecurityDeposit().removeListener(securityDepositAsDoubleListener); //dataModel.feeFromFundingTxProperty.removeListener(feeFromFundingTxListener); dataModel.getIsBtcWalletFunded().removeListener(isWalletFundedListener); @@ -593,7 +591,7 @@ boolean initWithData(OfferPayload.Direction direction, TradeCurrency tradeCurren amountDescription = Res.get("createOffer.amountPriceBox.amountDescription", isBuy ? Res.get("shared.buy") : Res.get("shared.sell")); - buyerSecurityDeposit.set(btcFormatter.formatCoin(dataModel.getBuyerSecurityDeposit().get())); + buyerSecurityDeposit.set(btcFormatter.formatToPercent(dataModel.getBuyerSecurityDeposit().get())); applyMakerFee(); return result; @@ -834,22 +832,22 @@ void onFocusOutBuyerSecurityDepositTextField(boolean oldValue, boolean newValue) InputValidator.ValidationResult result = securityDepositValidator.validate(buyerSecurityDeposit.get()); buyerSecurityDepositValidationResult.set(result); if (result.isValid) { - Coin defaultSecurityDeposit = Restrictions.getDefaultBuyerSecurityDeposit(); - String key = "buyerSecurityDepositLowerAsDefault"; - if (preferences.showAgain(key) && - btcFormatter.parseToCoin(buyerSecurityDeposit.get()).compareTo(defaultSecurityDeposit) < 0) { - final String postfix = dataModel.isBuyOffer() ? + double defaultSecurityDeposit = Restrictions.getDefaultBuyerSecurityDepositAsPercent(); + String key = "buyerSecurityDepositIsLowerAsDefault"; + double depositAsDouble = btcFormatter.parsePercentStringToDouble(buyerSecurityDeposit.get()); + if (preferences.showAgain(key) && depositAsDouble < defaultSecurityDeposit) { + String postfix = dataModel.isBuyOffer() ? Res.get("createOffer.tooLowSecDeposit.makerIsBuyer") : Res.get("createOffer.tooLowSecDeposit.makerIsSeller"); new Popup<>() .warning(Res.get("createOffer.tooLowSecDeposit.warning", - btcFormatter.formatCoinWithCode(defaultSecurityDeposit)) + "\n\n" + postfix) + btcFormatter.formatToPercentWithSymbol(defaultSecurityDeposit)) + "\n\n" + postfix) .width(800) .actionButtonText(Res.get("createOffer.resetToDefault")) .onAction(() -> { dataModel.setBuyerSecurityDeposit(defaultSecurityDeposit); ignoreSecurityDepositStringListener = true; - buyerSecurityDeposit.set(btcFormatter.formatCoin(dataModel.getBuyerSecurityDeposit().get())); + buyerSecurityDeposit.set(btcFormatter.formatToPercent(dataModel.getBuyerSecurityDeposit().get())); ignoreSecurityDepositStringListener = false; }) .closeButtonText(Res.get("createOffer.useLowerValue")) @@ -866,7 +864,7 @@ void onFocusOutBuyerSecurityDepositTextField(boolean oldValue, boolean newValue) private void applyBuyerSecurityDepositOnFocusOut() { setBuyerSecurityDepositToModel(); ignoreSecurityDepositStringListener = true; - buyerSecurityDeposit.set(btcFormatter.formatCoin(dataModel.getBuyerSecurityDeposit().get())); + buyerSecurityDeposit.set(btcFormatter.formatToPercent(dataModel.getBuyerSecurityDeposit().get())); ignoreSecurityDepositStringListener = false; } @@ -1091,13 +1089,12 @@ private void setVolumeToModel() { private void setBuyerSecurityDepositToModel() { if (buyerSecurityDeposit.get() != null && !buyerSecurityDeposit.get().isEmpty()) { - dataModel.setBuyerSecurityDeposit(btcFormatter.parseToCoinWith4Decimals(buyerSecurityDeposit.get())); + dataModel.setBuyerSecurityDeposit(btcFormatter.parsePercentStringToDouble(buyerSecurityDeposit.get())); } else { - dataModel.setBuyerSecurityDeposit(null); + dataModel.setBuyerSecurityDeposit(Restrictions.getDefaultBuyerSecurityDepositAsPercent()); } } - private InputValidator.ValidationResult isBtcInputValid(String input) { return btcValidator.validate(input); } diff --git a/desktop/src/main/java/bisq/desktop/main/portfolio/editoffer/EditOfferDataModel.java b/desktop/src/main/java/bisq/desktop/main/portfolio/editoffer/EditOfferDataModel.java index 6c34457146f..6fa8f90f1a8 100644 --- a/desktop/src/main/java/bisq/desktop/main/portfolio/editoffer/EditOfferDataModel.java +++ b/desktop/src/main/java/bisq/desktop/main/portfolio/editoffer/EditOfferDataModel.java @@ -39,6 +39,7 @@ import bisq.core.user.Preferences; import bisq.core.user.User; import bisq.core.util.BSFormatter; +import bisq.core.util.CoinUtil; import bisq.network.p2p.P2PService; @@ -98,7 +99,7 @@ public void reset() { minAmount.set(null); price.set(null); volume.set(null); - buyerSecurityDeposit.set(null); + buyerSecurityDeposit.set(0); paymentAccounts.clear(); paymentAccount = null; marketPriceMargin = 0; @@ -112,7 +113,7 @@ public void applyOpenOffer(OpenOffer openOffer) { CurrencyUtil.getTradeCurrency(offer.getCurrencyCode()) .ifPresent(c -> this.tradeCurrency = c); tradeCurrencyCode.set(offer.getCurrencyCode()); - buyerSecurityDeposit.set(offer.getBuyerSecurityDeposit()); + buyerSecurityDeposit.set(CoinUtil.getAsPercentPerBtc(offer.getBuyerSecurityDeposit())); this.initialState = openOffer.getState(); PaymentAccount tmpPaymentAccount = user.getPaymentAccount(openOffer.getOffer().getMakerPaymentAccountId()); 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 2ca19b0673b..8dd479f9212 100644 --- a/desktop/src/main/java/bisq/desktop/util/validation/SecurityDepositValidator.java +++ b/desktop/src/main/java/bisq/desktop/util/validation/SecurityDepositValidator.java @@ -21,17 +21,15 @@ import bisq.core.locale.Res; import bisq.core.util.BSFormatter; -import org.bitcoinj.core.Coin; - import javax.inject.Inject; -public class SecurityDepositValidator extends BtcValidator { +public class SecurityDepositValidator extends NumberValidator { + + private final BSFormatter formatter; @Inject public SecurityDepositValidator(BSFormatter formatter) { - super(formatter); - setMaxValue(Restrictions.getMaxBuyerSecurityDeposit()); - setMinValue(Restrictions.getMinBuyerSecurityDeposit()); + this.formatter = formatter; } @@ -46,22 +44,34 @@ public ValidationResult validate(String input) { if (result.isValid) { result = validateIfNotZero(input) .and(validateIfNotNegative(input)) - .and(validateIfNotTooLowBtcValue(input)) - .and(validateIfNotFractionalBtcValue(input)) - .and(validateIfNotExceedsMaxBtcValue(input)); + .and(validateIfNotTooLowPercentageValue(input)) + .and(validateIfNotTooHighPercentageValue(input)); } - return result; } - protected ValidationResult validateIfNotTooLowBtcValue(String input) { + private ValidationResult validateIfNotTooLowPercentageValue(String input) { + try { + double percentage = formatter.parsePercentStringToDouble(input); + double minPercentage = Restrictions.getMinBuyerSecurityDepositAsPercent(); + if (percentage < minPercentage) + return new ValidationResult(false, + Res.get("validation.inputTooSmall", formatter.formatToPercentWithSymbol(minPercentage))); + else + return new ValidationResult(true); + } catch (Throwable t) { + return new ValidationResult(false, Res.get("validation.invalidInput", t.getMessage())); + } + } + + private ValidationResult validateIfNotTooHighPercentageValue(String input) { try { - final Coin coin = Coin.parseCoin(input); - Coin minSecurityDeposit = Restrictions.getMinBuyerSecurityDeposit(); - if (coin.compareTo(minSecurityDeposit) < 0) + double percentage = formatter.parsePercentStringToDouble(input); + double maxPercentage = Restrictions.getMaxBuyerSecurityDepositAsPercent(); + if (percentage > maxPercentage) return new ValidationResult(false, - Res.get("validation.securityDeposit.toSmall", formatter.formatCoinWithCode(minSecurityDeposit))); + Res.get("validation.inputTooLarge", formatter.formatToPercentWithSymbol(maxPercentage))); else return new ValidationResult(true); } catch (Throwable t) { 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 7098c42637b..1d3d931fa31 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 void setUp() { when(btcWalletService.getOrCreateAddressEntry(anyString(), any())).thenReturn(addressEntry); when(preferences.isUsePercentageBasedPrice()).thenReturn(true); - when(preferences.getBuyerSecurityDepositAsCoin()).thenReturn(Coin.FIFTY_COINS); + when(preferences.getBuyerSecurityDepositAsPercent()).thenReturn(0.01); model = new CreateOfferDataModel(null, btcWalletService, null, preferences, user, null, From e04d042e4e245958ecaa139282dc041a0037f010 Mon Sep 17 00:00:00 2001 From: Manfred Karrer Date: Mon, 4 Mar 2019 23:48:41 -0500 Subject: [PATCH 2/6] Update security deposit values --- .../main/java/bisq/core/btc/wallet/Restrictions.java | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) 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 cd4ea3f022a..80054710deb 100644 --- a/core/src/main/java/bisq/core/btc/wallet/Restrictions.java +++ b/core/src/main/java/bisq/core/btc/wallet/Restrictions.java @@ -46,27 +46,27 @@ public static boolean isDust(Coin amount) { public static Coin getMinTradeAmount() { if (MIN_TRADE_AMOUNT == null) - MIN_TRADE_AMOUNT = Coin.valueOf(10_000); // 2 USD @ 20000 USD/BTC + MIN_TRADE_AMOUNT = Coin.valueOf(10_000); // 0,4 USD @ 4000 USD/BTC return MIN_TRADE_AMOUNT; } public static double getDefaultBuyerSecurityDepositAsPercent() { - return 0.01; // 1% of trade amount + return 0.02; // 2% of trade amount. For a 1 BTC trade it is about 80 USD @ 4000 USD/BTC. } public static double getMinBuyerSecurityDepositAsPercent() { - return 0.0005; // 0.05% of trade amount + return 0.0005; // 0.05% of trade amount. For a 1 BTC trade it is about 2 USD @ 4000 USD/BTC } public static double getMaxBuyerSecurityDepositAsPercent() { - return 0.1; // 10% of trade amount + return 0.1; // 10% of trade amount. For a 1 BTC trade it is about 400 USD @ 4000 USD/BTC } // We use MIN_BUYER_SECURITY_DEPOSIT as well as lower bound in case of small trade amounts. // So 0.0005 BTC is the min. buyer security deposit even with amount of 0.0001 BTC and 0.05% percentage value. public static Coin getMinBuyerSecurityDepositAsCoin() { if (MIN_BUYER_SECURITY_DEPOSIT == null) - MIN_BUYER_SECURITY_DEPOSIT = Coin.parseCoin("0.0005"); // 0.0005 BTC of a 1 BTC trade (0.05%) + MIN_BUYER_SECURITY_DEPOSIT = Coin.parseCoin("0.001"); // 0.001 BTC about 4 USD @ 4000 USD/BTC return MIN_BUYER_SECURITY_DEPOSIT; } @@ -77,7 +77,7 @@ public static double getSellerSecurityDepositAsPercent() { public static Coin getMinSellerSecurityDepositAsCoin() { if (SELLER_SECURITY_DEPOSIT == null) - SELLER_SECURITY_DEPOSIT = Coin.parseCoin("0.0005"); // 0.0005 BTC of a 1 BTC trade (0.05%) + SELLER_SECURITY_DEPOSIT = Coin.parseCoin("0.005"); // 0.005 BTC about 20 USD @ 4000 USD/BTC return SELLER_SECURITY_DEPOSIT; } } From cffa42a7a2a6262378f39a0af98828d9900447ca Mon Sep 17 00:00:00 2001 From: Manfred Karrer Date: Tue, 5 Mar 2019 00:04:24 -0500 Subject: [PATCH 3/6] Update security deposit values --- core/src/main/java/bisq/core/btc/wallet/Restrictions.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 80054710deb..528e8f04cbf 100644 --- a/core/src/main/java/bisq/core/btc/wallet/Restrictions.java +++ b/core/src/main/java/bisq/core/btc/wallet/Restrictions.java @@ -55,7 +55,7 @@ public static double getDefaultBuyerSecurityDepositAsPercent() { } public static double getMinBuyerSecurityDepositAsPercent() { - return 0.0005; // 0.05% of trade amount. For a 1 BTC trade it is about 2 USD @ 4000 USD/BTC + return 0.0005; // 0.05% of trade amount. For a 1 BTC trade it is about 2 USD @ 4000 USD/BTC but MIN_BUYER_SECURITY_DEPOSIT would require 0.001 BTC anyway (4 USD) } public static double getMaxBuyerSecurityDepositAsPercent() { @@ -72,7 +72,7 @@ public static Coin getMinBuyerSecurityDepositAsCoin() { public static double getSellerSecurityDepositAsPercent() { - return 0.003; + return 0.005; // 0.5% of trade amount. } public static Coin getMinSellerSecurityDepositAsCoin() { From 279b38bf2e2a0f1427929e1d7faf2281ba6064e7 Mon Sep 17 00:00:00 2001 From: Christoph Atteneder Date: Tue, 5 Mar 2019 13:07:24 +0100 Subject: [PATCH 4/6] Add information icon to buyer security deposit showing the deposit amount in BTC --- .../resources/i18n/displayStrings.properties | 1 + .../components/InfoInputTextField.java | 38 ++++++++++--------- .../main/offer/MutableOfferDataModel.java | 2 +- .../desktop/main/offer/MutableOfferView.java | 26 ++++++++++--- .../main/offer/MutableOfferViewModel.java | 15 ++++++-- 5 files changed, 53 insertions(+), 29 deletions(-) diff --git a/core/src/main/resources/i18n/displayStrings.properties b/core/src/main/resources/i18n/displayStrings.properties index dd354e45226..6605f585d82 100644 --- a/core/src/main/resources/i18n/displayStrings.properties +++ b/core/src/main/resources/i18n/displayStrings.properties @@ -417,6 +417,7 @@ createOffer.changePrice=Change price createOffer.tac=With publishing this offer I agree to trade with any trader who fulfills the conditions as defined in this screen. createOffer.currencyForFee=Trade fee createOffer.setDeposit=Set buyer's security deposit (%) +createOffer.securityDepositInfo=Your buyer''s security deposit will be {0} #################################################################### diff --git a/desktop/src/main/java/bisq/desktop/components/InfoInputTextField.java b/desktop/src/main/java/bisq/desktop/components/InfoInputTextField.java index 09a353041a5..9247b13d3d2 100644 --- a/desktop/src/main/java/bisq/desktop/components/InfoInputTextField.java +++ b/desktop/src/main/java/bisq/desktop/components/InfoInputTextField.java @@ -157,25 +157,27 @@ public final StringProperty textProperty() { private void setActionHandlers(Node node) { - currentIcon.setManaged(true); - currentIcon.setVisible(true); - - // As we don't use binding here we need to recreate it on mouse over to reflect the current state - currentIcon.setOnMouseEntered(e -> { - hidePopover = false; - showPopOver(node); - }); - currentIcon.setOnMouseExited(e -> { - if (popover != null) - popover.hide(); - hidePopover = true; - UserThread.runAfter(() -> { - if (hidePopover) { + if (node != null) { + currentIcon.setManaged(true); + currentIcon.setVisible(true); + + // As we don't use binding here we need to recreate it on mouse over to reflect the current state + currentIcon.setOnMouseEntered(e -> { + hidePopover = false; + showPopOver(node); + }); + currentIcon.setOnMouseExited(e -> { + if (popover != null) popover.hide(); - hidePopover = false; - } - }, 250, TimeUnit.MILLISECONDS); - }); + hidePopover = true; + UserThread.runAfter(() -> { + if (hidePopover) { + popover.hide(); + hidePopover = false; + } + }, 250, TimeUnit.MILLISECONDS); + }); + } } private void showPopOver(Node node) { 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 c591df7705c..1aa15a381dc 100644 --- a/desktop/src/main/java/bisq/desktop/main/offer/MutableOfferDataModel.java +++ b/desktop/src/main/java/bisq/desktop/main/offer/MutableOfferDataModel.java @@ -727,7 +727,7 @@ ReadOnlyDoubleProperty getBuyerSecurityDeposit() { return buyerSecurityDeposit; } - private Coin getBuyerSecurityDepositAsCoin() { + protected Coin getBuyerSecurityDepositAsCoin() { Coin percentOfAmountAsCoin = CoinUtil.getPercentOfAmountAsCoin(buyerSecurityDeposit.get(), amount.get()); return getBoundedBuyerSecurityDepositAsCoin(percentOfAmountAsCoin); } diff --git a/desktop/src/main/java/bisq/desktop/main/offer/MutableOfferView.java b/desktop/src/main/java/bisq/desktop/main/offer/MutableOfferView.java index 3daef035e71..f71c972a5f3 100644 --- a/desktop/src/main/java/bisq/desktop/main/offer/MutableOfferView.java +++ b/desktop/src/main/java/bisq/desktop/main/offer/MutableOfferView.java @@ -136,8 +136,9 @@ public abstract class MutableOfferView extends private BusyAnimation waitingForFundsSpinner; private AutoTooltipButton nextButton, cancelButton1, cancelButton2, placeOfferButton; private Button priceTypeToggleButton; - private InputTextField buyerSecurityDepositInputTextField, fixedPriceTextField, marketBasedPriceTextField; - protected InputTextField amountTextField, minAmountTextField, volumeTextField; + private InputTextField fixedPriceTextField; + private InputTextField marketBasedPriceTextField; + protected InputTextField amountTextField, minAmountTextField, volumeTextField, buyerSecurityDepositInputTextField; private TextField currencyTextField; private AddressTextField addressTextField; private BalanceTextField balanceTextField; @@ -161,7 +162,7 @@ public abstract class MutableOfferView extends priceAsPercentageFocusedListener, getShowWalletFundedNotificationListener, tradeFeeInBtcToggleListener, tradeFeeInBsqToggleListener, tradeFeeVisibleListener; private ChangeListener tradeCurrencyCodeListener, errorMessageListener, - marketPriceMarginListener, volumeListener; + marketPriceMarginListener, volumeListener, buyerSecurityDepositInBTCListener; private ChangeListener marketPriceAvailableListener; private EventHandler currencyComboBoxSelectionHandler, paymentAccountsComboBoxSelectionHandler; private OfferView.CloseHandler closeHandler; @@ -169,7 +170,8 @@ public abstract class MutableOfferView extends protected int gridRow = 0; private final List editOfferElements = new ArrayList<>(); private boolean clearXchangeWarningDisplayed, isActivated; - private InfoInputTextField marketBasedPriceInfoInputTextField, volumeInfoInputTextField; + private InfoInputTextField marketBasedPriceInfoInputTextField, volumeInfoInputTextField, + buyerSecurityDepositInfoInputTextField; private AutoTooltipSlideToggleButton tradeFeeInBtcToggle, tradeFeeInBsqToggle; private Text xIcon, fakeXIcon; @@ -761,6 +763,15 @@ private void createListeners() { } }; + buyerSecurityDepositInBTCListener = (observable, oldValue, newValue) -> { + if (!newValue.equals("")) { + Label depositInBTCInfo = createPopoverLabel(Res.get("createOffer.securityDepositInfo", newValue)); + buyerSecurityDepositInfoInputTextField.setContentForInfoPopOver(depositInBTCInfo); + } else { + buyerSecurityDepositInfoInputTextField.setContentForInfoPopOver(null); + } + }; + volumeListener = (observable, oldValue, newValue) -> { if (!newValue.equals("") && CurrencyUtil.isFiatCurrency(model.tradeCurrencyCode.get())) { volumeInfoInputTextField.setContentForPrivacyPopOver(createPopoverLabel(Res.get("offerbook.info.roundedFiatVolume"))); @@ -860,6 +871,7 @@ private void addListeners() { model.marketPriceMargin.addListener(marketPriceMarginListener); model.volume.addListener(volumeListener); model.isTradeFeeVisible.addListener(tradeFeeVisibleListener); + model.buyerSecurityDepositInBTC.addListener(buyerSecurityDepositInBTCListener); tradeFeeInBtcToggle.selectedProperty().addListener(tradeFeeInBtcToggleListener); tradeFeeInBsqToggle.selectedProperty().addListener(tradeFeeInBsqToggleListener); @@ -892,6 +904,7 @@ private void removeListeners() { model.marketPriceMargin.removeListener(marketPriceMarginListener); model.volume.removeListener(volumeListener); model.isTradeFeeVisible.removeListener(tradeFeeVisibleListener); + model.buyerSecurityDepositInBTC.removeListener(buyerSecurityDepositInBTCListener); tradeFeeInBtcToggle.selectedProperty().removeListener(tradeFeeInBtcToggleListener); tradeFeeInBsqToggle.selectedProperty().removeListener(tradeFeeInBsqToggleListener); @@ -1099,9 +1112,10 @@ private void showFeeOption() { } private VBox getBuyerSecurityDepositBox() { - Tuple3 tuple = getEditableValueBox( + Tuple3 tuple = getEditableValueBoxWithInfo( Res.get("createOffer.securityDeposit.prompt")); - buyerSecurityDepositInputTextField = tuple.second; + buyerSecurityDepositInfoInputTextField = tuple.second; + buyerSecurityDepositInputTextField = buyerSecurityDepositInfoInputTextField.getInputTextField(); Label buyerSecurityDepositPercentageLabel = tuple.third; // getEditableValueBox delivers BTC, so we overwrite it with % buyerSecurityDepositPercentageLabel.setText("%"); 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 96ba7d8f649..cf59a6ec1ca 100644 --- a/desktop/src/main/java/bisq/desktop/main/offer/MutableOfferViewModel.java +++ b/desktop/src/main/java/bisq/desktop/main/offer/MutableOfferViewModel.java @@ -105,6 +105,7 @@ public abstract class MutableOfferViewModel ext public final StringProperty amount = new SimpleStringProperty(); public final StringProperty minAmount = new SimpleStringProperty(); final StringProperty buyerSecurityDeposit = new SimpleStringProperty(); + final StringProperty buyerSecurityDepositInBTC = new SimpleStringProperty(); // Price in the viewModel is always dependent on fiat/altcoin: Fiat Fiat/BTC, for altcoins we use inverted price. // The domain (dataModel) uses always the same price model (otherCurrencyBTC) @@ -420,10 +421,13 @@ private void createListeners() { amountAsCoinListener = (ov, oldValue, newValue) -> { - if (newValue != null) + if (newValue != null) { amount.set(btcFormatter.formatCoin(newValue)); - else + buyerSecurityDepositInBTC.set(btcFormatter.formatCoinWithCode(dataModel.getBuyerSecurityDepositAsCoin())); + } else { amount.set(""); + buyerSecurityDepositInBTC.set(""); + } applyMakerFee(); }; @@ -455,10 +459,13 @@ private void createListeners() { }; securityDepositAsDoubleListener = (ov, oldValue, newValue) -> { - if (newValue != null) + if (newValue != null) { buyerSecurityDeposit.set(btcFormatter.formatToPercent((double) newValue)); - else + buyerSecurityDepositInBTC.set(btcFormatter.formatCoinWithCode(dataModel.getBuyerSecurityDepositAsCoin())); + } else { buyerSecurityDeposit.set(""); + buyerSecurityDepositInBTC.set(""); + } }; From 95d8f514adf58cc6d6f9e7e214d98e186a633c2a Mon Sep 17 00:00:00 2001 From: Christoph Atteneder Date: Tue, 5 Mar 2019 13:08:26 +0100 Subject: [PATCH 5/6] Add news badge for new percentage security deposit --- desktop/src/main/java/bisq/desktop/bisq.css | 2 +- .../bisq/desktop/components/NewBadge.java | 34 +++++++++++++++++++ .../desktop/main/offer/MutableOfferView.java | 9 ++++- .../bisq/desktop/main/offer/OfferView.java | 4 +++ 4 files changed, 47 insertions(+), 2 deletions(-) create mode 100644 desktop/src/main/java/bisq/desktop/components/NewBadge.java diff --git a/desktop/src/main/java/bisq/desktop/bisq.css b/desktop/src/main/java/bisq/desktop/bisq.css index dd5f2f9c5be..b2ff670a49d 100644 --- a/desktop/src/main/java/bisq/desktop/bisq.css +++ b/desktop/src/main/java/bisq/desktop/bisq.css @@ -530,7 +530,7 @@ bg color of non edit textFields: fafafa -fx-pref-width: 30; } -.jfx-badge .label { +.jfx-badge .badge-pane .label { -fx-font-weight: bold; -fx-font-size: 0.692em; -fx-text-fill: -bs-rd-white; diff --git a/desktop/src/main/java/bisq/desktop/components/NewBadge.java b/desktop/src/main/java/bisq/desktop/components/NewBadge.java new file mode 100644 index 00000000000..80a1d48a0fb --- /dev/null +++ b/desktop/src/main/java/bisq/desktop/components/NewBadge.java @@ -0,0 +1,34 @@ +package bisq.desktop.components; + +import bisq.core.locale.Res; +import bisq.core.user.Preferences; + +import com.jfoenix.controls.JFXBadge; + +import javafx.scene.Node; + +import javafx.collections.MapChangeListener; + +public class NewBadge extends JFXBadge { + + private final String key; + + public NewBadge(Node control, String key, Preferences preferences) { + super(control); + + this.key = key; + + setText(Res.get("shared.new")); + getStyleClass().add("new"); + + setEnabled(!preferences.getDontShowAgainMap().containsKey(key)); + refreshBadge(); + + preferences.getDontShowAgainMapAsObservable().addListener((MapChangeListener) change -> { + if (change.getKey().equals(key)) { + setEnabled(!change.wasAdded()); + refreshBadge(); + } + }); + } +} diff --git a/desktop/src/main/java/bisq/desktop/main/offer/MutableOfferView.java b/desktop/src/main/java/bisq/desktop/main/offer/MutableOfferView.java index f71c972a5f3..de1313b102a 100644 --- a/desktop/src/main/java/bisq/desktop/main/offer/MutableOfferView.java +++ b/desktop/src/main/java/bisq/desktop/main/offer/MutableOfferView.java @@ -28,6 +28,7 @@ import bisq.desktop.components.FundsTextField; import bisq.desktop.components.InfoInputTextField; import bisq.desktop.components.InputTextField; +import bisq.desktop.components.NewBadge; import bisq.desktop.components.TitledGroupBg; import bisq.desktop.main.MainView; import bisq.desktop.main.account.AccountView; @@ -122,6 +123,7 @@ import static javafx.beans.binding.Bindings.createStringBinding; public abstract class MutableOfferView extends ActivatableViewAndModel { + public static final String BUYER_SECURITY_DEPOSIT_NEWS = "buyerSecurityDepositNews0.9.5"; protected final Navigation navigation; private final Preferences preferences; private final Transitions transitions; @@ -1042,7 +1044,12 @@ private void addOptionsGroup() { GridPane.setMargin(advancedOptionsBox, new Insets(Layout.COMPACT_FIRST_ROW_AND_GROUP_DISTANCE, 0, 0, 0)); gridPane.getChildren().add(advancedOptionsBox); - advancedOptionsBox.getChildren().addAll(getBuyerSecurityDepositBox(), getTradeFeeFieldsBox()); + // add new badge for this new feature for this release + // TODO: remove it with 0.9.6+ + NewBadge securityDepositBoxWithNewBadge = new NewBadge(getBuyerSecurityDepositBox(), + BUYER_SECURITY_DEPOSIT_NEWS, preferences); + + advancedOptionsBox.getChildren().addAll(securityDepositBoxWithNewBadge, getTradeFeeFieldsBox()); Tuple2 tuple = add2ButtonsAfterGroup(gridPane, ++gridRow, diff --git a/desktop/src/main/java/bisq/desktop/main/offer/OfferView.java b/desktop/src/main/java/bisq/desktop/main/offer/OfferView.java index 84313552c81..d2fa0955da6 100644 --- a/desktop/src/main/java/bisq/desktop/main/offer/OfferView.java +++ b/desktop/src/main/java/bisq/desktop/main/offer/OfferView.java @@ -51,6 +51,8 @@ import java.util.Optional; import java.util.stream.Collectors; +import static bisq.desktop.main.offer.MutableOfferView.BUYER_SECURITY_DEPOSIT_NEWS; + public abstract class OfferView extends ActivatableView { private OfferBookView offerBookView; @@ -271,6 +273,8 @@ private void onCreateOfferViewRemoved() { offerBookView.enableCreateOfferButton(); navigation.navigateTo(MainView.class, this.getClass(), OfferBookView.class); + + preferences.dontShowAgain(BUYER_SECURITY_DEPOSIT_NEWS, true); } private void onTakeOfferViewRemoved() { From 549348aee0bda7db0516744ce5721fa8075e727d Mon Sep 17 00:00:00 2001 From: Christoph Atteneder Date: Tue, 5 Mar 2019 15:02:37 +0100 Subject: [PATCH 6/6] Add news badge for instant trade --- .../components/paymentmethods/AssetsForm.java | 30 +++++++++++++++++-- .../altcoinaccounts/AltCoinAccountsView.java | 13 ++++++-- 2 files changed, 39 insertions(+), 4 deletions(-) diff --git a/desktop/src/main/java/bisq/desktop/components/paymentmethods/AssetsForm.java b/desktop/src/main/java/bisq/desktop/components/paymentmethods/AssetsForm.java index 140ff98c2d4..76bad08bbed 100644 --- a/desktop/src/main/java/bisq/desktop/components/paymentmethods/AssetsForm.java +++ b/desktop/src/main/java/bisq/desktop/components/paymentmethods/AssetsForm.java @@ -18,6 +18,7 @@ package bisq.desktop.components.paymentmethods; import bisq.desktop.components.InputTextField; +import bisq.desktop.components.NewBadge; import bisq.desktop.main.overlays.popups.Popup; import bisq.desktop.util.FormBuilder; import bisq.desktop.util.Layout; @@ -34,6 +35,7 @@ import bisq.core.payment.payload.AssetsAccountPayload; import bisq.core.payment.payload.PaymentAccountPayload; import bisq.core.payment.validation.AltCoinAddressValidator; +import bisq.core.user.Preferences; import bisq.core.util.BSFormatter; import bisq.core.util.validation.InputValidator; @@ -45,8 +47,13 @@ import javafx.scene.control.Label; import javafx.scene.control.TextField; import javafx.scene.layout.GridPane; +import javafx.scene.layout.Priority; +import javafx.scene.layout.Region; import javafx.scene.layout.VBox; +import javafx.geometry.Insets; +import javafx.geometry.Pos; + import javafx.collections.FXCollections; import javafx.util.StringConverter; @@ -55,14 +62,17 @@ import static bisq.desktop.util.FormBuilder.addCompactTopLabelTextField; import static bisq.desktop.util.FormBuilder.addCompactTopLabelTextFieldWithCopyIcon; +import static bisq.desktop.util.FormBuilder.addLabelCheckBox; import static bisq.desktop.util.FormBuilder.addTopLabelTextField; import static bisq.desktop.util.GUIUtil.getComboBoxButtonCell; public class AssetsForm extends PaymentMethodForm { + public static final String INSTANT_TRADE_NEWS = "instantTradeNews0.9.5"; private final AssetAccount assetAccount; private final AltCoinAddressValidator altCoinAddressValidator; private final AssetService assetService; private final FilterManager filterManager; + private final Preferences preferences; private InputTextField addressInputTextField; private CheckBox tradeInstantCheckBox; @@ -85,12 +95,14 @@ public AssetsForm(PaymentAccount paymentAccount, int gridRow, BSFormatter formatter, AssetService assetService, - FilterManager filterManager) { + FilterManager filterManager, + Preferences preferences) { super(paymentAccount, accountAgeWitnessService, inputValidator, gridPane, gridRow, formatter); this.assetAccount = (AssetAccount) paymentAccount; this.altCoinAddressValidator = altCoinAddressValidator; this.assetService = assetService; this.filterManager = filterManager; + this.preferences = preferences; tradeInstant = paymentAccount instanceof InstantCryptoCurrencyAccount; } @@ -102,7 +114,7 @@ public void addFormForAddAccount() { addTradeCurrencyComboBox(); currencyComboBox.setPrefWidth(250); - tradeInstantCheckBox = FormBuilder.addLabelCheckBox(gridPane, ++gridRow, + tradeInstantCheckBox = addLabelCheckBox(gridPane, ++gridRow, Res.get("payment.altcoin.tradeInstantCheckbox"), 10); tradeInstantCheckBox.setSelected(tradeInstant); tradeInstantCheckBox.setOnAction(e -> { @@ -111,6 +123,20 @@ public void addFormForAddAccount() { new Popup<>().information(Res.get("payment.altcoin.tradeInstant.popup")).show(); }); + // add new badge for this new feature for this release + // TODO: remove it with 0.9.6+ + gridPane.getChildren().remove(tradeInstantCheckBox); + tradeInstantCheckBox.setPadding(new Insets(0, 40, 0, 0)); + + NewBadge instantTradeNewsBadge = new NewBadge(tradeInstantCheckBox, INSTANT_TRADE_NEWS, preferences); + instantTradeNewsBadge.setAlignment(Pos.CENTER_LEFT); + instantTradeNewsBadge.setMaxSize(Region.USE_PREF_SIZE, Region.USE_PREF_SIZE); + + GridPane.setRowIndex(instantTradeNewsBadge, gridRow); + GridPane.setHgrow(instantTradeNewsBadge, Priority.NEVER); + GridPane.setMargin(instantTradeNewsBadge, new Insets(10, 0, 0, 0)); + gridPane.getChildren().add(instantTradeNewsBadge); + addressInputTextField = FormBuilder.addInputTextField(gridPane, ++gridRow, Res.get("payment.altcoin.address")); addressInputTextField.setValidator(altCoinAddressValidator); diff --git a/desktop/src/main/java/bisq/desktop/main/account/content/altcoinaccounts/AltCoinAccountsView.java b/desktop/src/main/java/bisq/desktop/main/account/content/altcoinaccounts/AltCoinAccountsView.java index 0a3a2f63369..0ca30300bcf 100644 --- a/desktop/src/main/java/bisq/desktop/main/account/content/altcoinaccounts/AltCoinAccountsView.java +++ b/desktop/src/main/java/bisq/desktop/main/account/content/altcoinaccounts/AltCoinAccountsView.java @@ -37,6 +37,7 @@ import bisq.core.payment.PaymentAccountFactory; import bisq.core.payment.payload.PaymentMethod; import bisq.core.payment.validation.AltCoinAddressValidator; +import bisq.core.user.Preferences; import bisq.core.util.BSFormatter; import bisq.core.util.validation.InputValidator; @@ -60,6 +61,7 @@ import java.util.Optional; +import static bisq.desktop.components.paymentmethods.AssetsForm.INSTANT_TRADE_NEWS; import static bisq.desktop.util.FormBuilder.add2ButtonsAfterGroup; import static bisq.desktop.util.FormBuilder.add3ButtonsAfterGroup; import static bisq.desktop.util.FormBuilder.addTitledGroupBg; @@ -74,6 +76,7 @@ public class AltCoinAccountsView extends PaymentAccountsView().warning(Res.get("shared.accountNameAlreadyUsed")).show(); } + + preferences.dontShowAgain(INSTANT_TRADE_NEWS, true); } } private void onCancelNewAccount() { removeNewAccountForm(); + + preferences.dontShowAgain(INSTANT_TRADE_NEWS, true); } /////////////////////////////////////////////////////////////////////////////////////////// @@ -228,7 +237,7 @@ private PaymentMethodForm getPaymentMethodForm(PaymentMethod paymentMethod) { private PaymentMethodForm getPaymentMethodForm(PaymentAccount paymentAccount) { return new AssetsForm(paymentAccount, accountAgeWitnessService, altCoinAddressValidator, - inputValidator, root, gridRow, formatter, assetService, filterManager); + inputValidator, root, gridRow, formatter, assetService, filterManager, preferences); } private void removeNewAccountForm() {