Merge pull request #2498 from ManfredKarrer/make-buyer-security-deposit-percentage-based

Use percentage based value for security deposits
This commit is contained in:
Manfred Karrer 2019-03-05 09:47:24 -05:00 committed by GitHub
commit ac3cae101e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 308 additions and 135 deletions

View File

@ -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;
}
///////////////////////////////////////////////////////////////////////////////////////////

View File

@ -23,9 +23,7 @@ import org.bitcoinj.core.Coin;
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;
@ -48,32 +46,38 @@ public class Restrictions {
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;
}
// 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.02; // 2% of trade amount. For a 1 BTC trade it is about 80 USD @ 4000 USD/BTC.
}
public static Coin getMinBuyerSecurityDeposit() {
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 but MIN_BUYER_SECURITY_DEPOSIT would require 0.001 BTC anyway (4 USD)
}
public static double getMaxBuyerSecurityDepositAsPercent() {
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.valueOf(50_000); // 10 USD @ 20000 USD/BTC
MIN_BUYER_SECURITY_DEPOSIT = Coin.parseCoin("0.001"); // 0.001 BTC about 4 USD @ 4000 USD/BTC
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.005; // 0.5% of trade amount.
}
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.005"); // 0.005 BTC about 20 USD @ 4000 USD/BTC
return SELLER_SECURITY_DEPOSIT;
}
}

View File

@ -347,18 +347,18 @@ public class OfferUtil {
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()),

View File

@ -619,9 +619,9 @@ public abstract class Trade implements Tradable, Model {
///////////////////////////////////////////////////////////////////////////////////////////
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);
}

View File

@ -39,8 +39,6 @@ import bisq.common.proto.persistable.PersistedDataHost;
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 final class Preferences implements PersistedDataHost, BridgeAddressProvid
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 final class Preferences implements PersistedDataHost, BridgeAddressProvid
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 @@ public final class Preferences implements PersistedDataHost, BridgeAddressProvid
void setWithdrawalTxFeeInBytes(long withdrawalTxFeeInBytes);
void setBuyerSecurityDepositAsLong(long buyerSecurityDepositAsLong);
void setSelectedPaymentAccountForCreateOffer(@Nullable PaymentAccount paymentAccount);
void setBsqBlockChainExplorer(BlockChainExplorer bsqBlockChainExplorer);
@ -832,5 +829,9 @@ public final class Preferences implements PersistedDataHost, BridgeAddressProvid
void setRpcPw(String value);
void setTakeOfferSelectedPaymentAccountId(String value);
void setBuyerSecurityDepositAsPercent(double buyerSecurityDepositAsPercent);
double getBuyerSecurityDepositAsPercent();
}
}

View File

@ -89,7 +89,10 @@ public final class PreferencesPayload implements PersistableEnvelope {
private String bitcoinNodes = "";
private List<String> 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 final class PreferencesPayload implements PersistableEnvelope {
.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 final class PreferencesPayload implements PersistableEnvelope {
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());
}
}

View File

@ -41,4 +41,24 @@ public class CoinUtil {
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));
}
}

View File

@ -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,8 @@ 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 (%)
createOffer.securityDepositInfo=Your buyer''s security deposit will be {0}
####################################################################

View File

@ -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;

View File

@ -157,25 +157,27 @@ public class InfoInputTextField extends AnchorPane {
private void setActionHandlers(Node node) {
currentIcon.setManaged(true);
currentIcon.setVisible(true);
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 = true;
UserThread.runAfter(() -> {
if (hidePopover) {
// 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) {

View File

@ -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<? super String, ? super Boolean>) change -> {
if (change.getKey().equals(key)) {
setEnabled(!change.wasAdded());
refreshBadge();
}
});
}
}

View File

@ -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.PaymentAccount;
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.CheckBox;
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 java.util.Optional;
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 class AssetsForm extends PaymentMethodForm {
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 class AssetsForm extends PaymentMethodForm {
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 class AssetsForm extends PaymentMethodForm {
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);

View File

@ -37,6 +37,7 @@ import bisq.core.payment.PaymentAccount;
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 javafx.collections.ObservableList;
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<GridPane, AltCoinAc
private final AssetService assetService;
private final FilterManager filterManager;
private final BSFormatter formatter;
private final Preferences preferences;
private PaymentMethodForm paymentMethodForm;
private TitledGroupBg accountTitledGroupBg;
@ -87,7 +90,8 @@ public class AltCoinAccountsView extends PaymentAccountsView<GridPane, AltCoinAc
AccountAgeWitnessService accountAgeWitnessService,
AssetService assetService,
FilterManager filterManager,
BSFormatter formatter) {
BSFormatter formatter,
Preferences preferences) {
super(model);
this.inputValidator = inputValidator;
@ -96,6 +100,7 @@ public class AltCoinAccountsView extends PaymentAccountsView<GridPane, AltCoinAc
this.assetService = assetService;
this.filterManager = filterManager;
this.formatter = formatter;
this.preferences = preferences;
}
@Override
@ -148,11 +153,15 @@ public class AltCoinAccountsView extends PaymentAccountsView<GridPane, AltCoinAc
} else {
new Popup<>().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 @@ public class AltCoinAccountsView extends PaymentAccountsView<GridPane, AltCoinAc
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() {

View File

@ -46,6 +46,7 @@ import bisq.core.trade.statistics.ReferralIdService;
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.inject.Inject;
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<PaymentAccount> 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<Coin> minAmount = new SimpleObjectProperty<>();
protected final ObjectProperty<Price> price = new SimpleObjectProperty<>();
protected final ObjectProperty<Volume> volume = new SimpleObjectProperty<>();
protected final ObjectProperty<Coin> 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<PaymentAccount> paymentAccounts = FXCollections.observableArrayList();
@ -174,8 +179,8 @@ public abstract class MutableOfferDataModel extends OfferDataModel implements Bs
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 @@ public abstract class MutableOfferDataModel extends OfferDataModel implements Bs
user.getPaymentAccountsAsObservable().addListener(paymentAccountsChangeListener);
}
private void removeListeners() {
btcWalletService.removeBalanceListener(btcBalanceListener);
bsqWalletService.removeBsqBalanceListener(this);
user.getPaymentAccountsAsObservable().removeListener(paymentAccountsChangeListener);
}
///////////////////////////////////////////////////////////////////////////////////////////
// API
///////////////////////////////////////////////////////////////////////////////////////////
@ -316,8 +321,8 @@ public abstract class MutableOfferDataModel extends OfferDataModel implements Bs
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<String> acceptedCountryCodes = PaymentAccountUtil.getAcceptedCountryCodes(paymentAccount);
List<String> acceptedBanks = PaymentAccountUtil.getAcceptedBanks(paymentAccount);
@ -335,7 +340,7 @@ public abstract class MutableOfferDataModel extends OfferDataModel implements Bs
long lowerClosePrice = 0;
long upperClosePrice = 0;
String hashOfChallenge = null;
Coin buyerSecurityDepositAsCoin = buyerSecurityDeposit.get();
Coin makerFeeAsCoin = getMakerFee();
Map<String, String> extraDataMap = OfferUtil.getExtraDataMap(accountAgeWitnessService,
@ -345,7 +350,7 @@ public abstract class MutableOfferDataModel extends OfferDataModel implements Bs
OfferUtil.validateOfferData(filterManager,
p2PService,
buyerSecurityDepositAsCoin,
buyerSecurityDeposit.get(),
paymentAccount,
currencyCode,
makerFeeAsCoin);
@ -358,8 +363,8 @@ public abstract class MutableOfferDataModel extends OfferDataModel implements Bs
priceAsLong,
marketPriceMarginParam,
useMarketBasedPriceValue,
amount,
minAmount,
amountAsLong,
minAmountAsLong,
baseCurrencyCode,
counterCurrencyCode,
Lists.newArrayList(user.getAcceptedArbitratorAddresses()),
@ -376,8 +381,8 @@ public abstract class MutableOfferDataModel extends OfferDataModel implements Bs
txFeeFromFeeService.value,
makerFeeAsCoin.value,
isCurrencyForMakerFeeBtc(),
buyerSecurityDepositAsCoin.value,
sellerSecurityDeposit.value,
getBuyerSecurityDepositAsCoin().value,
getSellerSecurityDepositAsCoin().value,
maxTradeLimit,
maxTradePeriod,
useAutoClose,
@ -642,7 +647,7 @@ public abstract class MutableOfferDataModel extends OfferDataModel implements Bs
}
Coin getSecurityDeposit() {
return isBuyOffer() ? buyerSecurityDeposit.get() : sellerSecurityDeposit;
return isBuyOffer() ? getBuyerSecurityDepositAsCoin() : getSellerSecurityDepositAsCoin();
}
public boolean isBuyOffer() {
@ -677,9 +682,9 @@ public abstract class MutableOfferDataModel extends OfferDataModel implements Bs
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 @@ public abstract class MutableOfferDataModel extends OfferDataModel implements Bs
return useMarketBasedPrice;
}
ReadOnlyObjectProperty<Coin> getBuyerSecurityDeposit() {
ReadOnlyDoubleProperty getBuyerSecurityDeposit() {
return buyerSecurityDeposit;
}
Coin getSellerSecurityDeposit() {
return sellerSecurityDeposit;
protected 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<Coin> totalToPayAsCoinProperty() {

View File

@ -28,6 +28,7 @@ import bisq.desktop.components.BusyAnimation;
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 bisq.desktop.util.FormBuilder.*;
import static javafx.beans.binding.Bindings.createStringBinding;
public abstract class MutableOfferView<M extends MutableOfferViewModel> extends ActivatableViewAndModel<AnchorPane, M> {
public static final String BUYER_SECURITY_DEPOSIT_NEWS = "buyerSecurityDepositNews0.9.5";
protected final Navigation navigation;
private final Preferences preferences;
private final Transitions transitions;
@ -136,8 +138,9 @@ public abstract class MutableOfferView<M extends MutableOfferViewModel> 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 +164,7 @@ public abstract class MutableOfferView<M extends MutableOfferViewModel> extends
priceAsPercentageFocusedListener, getShowWalletFundedNotificationListener,
tradeFeeInBtcToggleListener, tradeFeeInBsqToggleListener, tradeFeeVisibleListener;
private ChangeListener<String> tradeCurrencyCodeListener, errorMessageListener,
marketPriceMarginListener, volumeListener;
marketPriceMarginListener, volumeListener, buyerSecurityDepositInBTCListener;
private ChangeListener<Number> marketPriceAvailableListener;
private EventHandler<ActionEvent> currencyComboBoxSelectionHandler, paymentAccountsComboBoxSelectionHandler;
private OfferView.CloseHandler closeHandler;
@ -169,7 +172,8 @@ public abstract class MutableOfferView<M extends MutableOfferViewModel> extends
protected int gridRow = 0;
private final List<Node> 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 +765,15 @@ public abstract class MutableOfferView<M extends MutableOfferViewModel> extends
}
};
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 +873,7 @@ public abstract class MutableOfferView<M extends MutableOfferViewModel> extends
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 +906,7 @@ public abstract class MutableOfferView<M extends MutableOfferViewModel> extends
model.marketPriceMargin.removeListener(marketPriceMarginListener);
model.volume.removeListener(volumeListener);
model.isTradeFeeVisible.removeListener(tradeFeeVisibleListener);
model.buyerSecurityDepositInBTC.removeListener(buyerSecurityDepositInBTCListener);
tradeFeeInBtcToggle.selectedProperty().removeListener(tradeFeeInBtcToggleListener);
tradeFeeInBsqToggle.selectedProperty().removeListener(tradeFeeInBsqToggleListener);
@ -1029,7 +1044,12 @@ public abstract class MutableOfferView<M extends MutableOfferViewModel> extends
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<Button, Button> tuple = add2ButtonsAfterGroup(gridPane, ++gridRow,
@ -1099,16 +1119,19 @@ public abstract class MutableOfferView<M extends MutableOfferViewModel> extends
}
private VBox getBuyerSecurityDepositBox() {
Tuple3<HBox, InputTextField, Label> tuple = getEditableValueBox(
Tuple3<HBox, InfoInputTextField, Label> tuple = getEditableValueBoxWithInfo(
Res.get("createOffer.securityDeposit.prompt"));
buyerSecurityDepositInputTextField = tuple.second;
Label buyerSecurityDepositBtcLabel = tuple.third;
buyerSecurityDepositInfoInputTextField = tuple.second;
buyerSecurityDepositInputTextField = buyerSecurityDepositInfoInputTextField.getInputTextField();
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;
}

View File

@ -105,7 +105,7 @@ public abstract class MutableOfferViewModel<M extends MutableOfferDataModel> ext
public final StringProperty amount = new SimpleStringProperty();
public final StringProperty minAmount = new SimpleStringProperty();
final StringProperty buyerSecurityDeposit = new SimpleStringProperty();
final String sellerSecurityDeposit;
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)
@ -158,7 +158,7 @@ public abstract class MutableOfferViewModel<M extends MutableOfferDataModel> ext
private ChangeListener<Coin> minAmountAsCoinListener;
private ChangeListener<Price> priceListener;
private ChangeListener<Volume> volumeListener;
private ChangeListener<Coin> securityDepositAsCoinListener;
private ChangeListener<Number> securityDepositAsDoubleListener;
private ChangeListener<Boolean> isWalletFundedListener;
//private ChangeListener<Coin> feeFromFundingTxListener;
@ -209,7 +209,6 @@ public abstract class MutableOfferViewModel<M extends MutableOfferDataModel> ext
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 +222,7 @@ public abstract class MutableOfferViewModel<M extends MutableOfferDataModel> ext
if (DevEnv.isDevMode()) {
UserThread.runAfter(() -> {
amount.set("1");
price.set("0.0002");
price.set("0.03");
minAmount.set(amount.get());
onFocusOutPriceAsPercentageTextField(true, false);
applyMakerFee();
@ -422,10 +421,13 @@ public abstract class MutableOfferViewModel<M extends MutableOfferDataModel> ext
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();
};
@ -456,11 +458,14 @@ public abstract class MutableOfferViewModel<M extends MutableOfferDataModel> ext
applyMakerFee();
};
securityDepositAsCoinListener = (ov, oldValue, newValue) -> {
if (newValue != null)
buyerSecurityDeposit.set(btcFormatter.formatCoin(newValue));
else
securityDepositAsDoubleListener = (ov, oldValue, newValue) -> {
if (newValue != null) {
buyerSecurityDeposit.set(btcFormatter.formatToPercent((double) newValue));
buyerSecurityDepositInBTC.set(btcFormatter.formatCoinWithCode(dataModel.getBuyerSecurityDepositAsCoin()));
} else {
buyerSecurityDeposit.set("");
buyerSecurityDepositInBTC.set("");
}
};
@ -543,7 +548,7 @@ public abstract class MutableOfferViewModel<M extends MutableOfferDataModel> ext
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 +570,7 @@ public abstract class MutableOfferViewModel<M extends MutableOfferDataModel> ext
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 +598,7 @@ public abstract class MutableOfferViewModel<M extends MutableOfferDataModel> ext
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 +839,22 @@ public abstract class MutableOfferViewModel<M extends MutableOfferDataModel> ext
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 +871,7 @@ public abstract class MutableOfferViewModel<M extends MutableOfferDataModel> ext
private void applyBuyerSecurityDepositOnFocusOut() {
setBuyerSecurityDepositToModel();
ignoreSecurityDepositStringListener = true;
buyerSecurityDeposit.set(btcFormatter.formatCoin(dataModel.getBuyerSecurityDeposit().get()));
buyerSecurityDeposit.set(btcFormatter.formatToPercent(dataModel.getBuyerSecurityDeposit().get()));
ignoreSecurityDepositStringListener = false;
}
@ -1091,13 +1096,12 @@ public abstract class MutableOfferViewModel<M extends MutableOfferDataModel> ext
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);
}

View File

@ -51,6 +51,8 @@ import java.util.List;
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<TabPane, Void> {
private OfferBookView offerBookView;
@ -271,6 +273,8 @@ public abstract class OfferView extends ActivatableView<TabPane, Void> {
offerBookView.enableCreateOfferButton();
navigation.navigateTo(MainView.class, this.getClass(), OfferBookView.class);
preferences.dontShowAgain(BUYER_SECURITY_DEPOSIT_NEWS, true);
}
private void onTakeOfferViewRemoved() {

View File

@ -39,6 +39,7 @@ import bisq.core.trade.statistics.ReferralIdService;
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 @@ class EditOfferDataModel extends MutableOfferDataModel {
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 @@ class EditOfferDataModel extends MutableOfferDataModel {
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());

View File

@ -21,17 +21,15 @@ import bisq.core.btc.wallet.Restrictions;
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 class SecurityDepositValidator extends BtcValidator {
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 {
final Coin coin = Coin.parseCoin(input);
Coin minSecurityDeposit = Restrictions.getMinBuyerSecurityDeposit();
if (coin.compareTo(minSecurityDeposit) < 0)
double percentage = formatter.parsePercentStringToDouble(input);
double minPercentage = Restrictions.getMinBuyerSecurityDepositAsPercent();
if (percentage < minPercentage)
return new ValidationResult(false,
Res.get("validation.securityDeposit.toSmall", formatter.formatCoinWithCode(minSecurityDeposit)));
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 {
double percentage = formatter.parsePercentStringToDouble(input);
double maxPercentage = Restrictions.getMaxBuyerSecurityDepositAsPercent();
if (percentage > maxPercentage)
return new ValidationResult(false,
Res.get("validation.inputTooLarge", formatter.formatToPercentWithSymbol(maxPercentage)));
else
return new ValidationResult(true);
} catch (Throwable t) {

View File

@ -66,7 +66,7 @@ public class CreateOfferDataModelTest {
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,