Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Display minimum limit if used during trade #3870

Merged
merged 5 commits into from Jan 7, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
5 changes: 3 additions & 2 deletions core/src/main/resources/i18n/displayStrings.properties
Expand Up @@ -189,7 +189,7 @@ shared.makerTxFee=Maker: {0}
shared.takerTxFee=Taker: {0}
shared.securityDepositBox.description=Security deposit for BTC {0}
shared.iConfirm=I confirm
shared.tradingFeeInBsqInfo=equivalent to {0} used as mining fee
shared.tradingFeeInBsqInfo=equivalent to {0} used as trading fee
shared.openURL=Open {0}
shared.fiat=Fiat
shared.crypto=Crypto
Expand Down Expand Up @@ -479,6 +479,7 @@ createOffer.setDeposit=Set buyer's security deposit (%)
createOffer.setDepositAsBuyer=Set my security deposit as buyer (%)
createOffer.securityDepositInfo=Your buyer''s security deposit will be {0}
createOffer.securityDepositInfoAsBuyer=Your security deposit as buyer will be {0}
createOffer.minSecurityDepositUsed=Min. buyer security deposit is used


####################################################################
Expand Down Expand Up @@ -2738,7 +2739,7 @@ URL: \"{0}\"
guiUtil.openWebBrowser.doOpen=Open the web page and don't ask again
guiUtil.openWebBrowser.copyUrl=Copy URL and cancel
guiUtil.ofTradeAmount=of trade amount

guiUtil.requiredMinimum=(required minimum)

####################################################################
# Component specific
Expand Down
Expand Up @@ -713,4 +713,8 @@ boolean canPlaceOffer() {
return GUIUtil.isBootstrappedOrShowPopup(p2PService) &&
GUIUtil.canCreateOrTakeOfferOrShowPopup(user, navigation);
}

public boolean isMinBuyerSecurityDeposit() {
return !getBuyerSecurityDepositAsCoin().isGreaterThan(Restrictions.getMinBuyerSecurityDepositAsCoin());
}
}
Expand Up @@ -144,7 +144,8 @@ public abstract class MutableOfferView<M extends MutableOfferViewModel<?>> exten
private FundsTextField totalToPayTextField;
private Label amountDescriptionLabel, priceCurrencyLabel, priceDescriptionLabel, volumeDescriptionLabel,
waitingForFundsLabel, marketBasedPriceLabel, percentagePriceDescription, tradeFeeDescriptionLabel,
resultLabel, tradeFeeInBtcLabel, tradeFeeInBsqLabel, xLabel, fakeXLabel, buyerSecurityDepositLabel;
resultLabel, tradeFeeInBtcLabel, tradeFeeInBsqLabel, xLabel, fakeXLabel, buyerSecurityDepositLabel,
buyerSecurityDepositPercentageLabel;
protected Label amountBtcLabel, volumeCurrencyLabel, minAmountBtcLabel;
private ComboBox<PaymentAccount> paymentAccountsComboBox;
private ComboBox<TradeCurrency> currencyComboBox;
Expand All @@ -159,7 +160,8 @@ public abstract class MutableOfferView<M extends MutableOfferViewModel<?>> exten
private ChangeListener<Boolean> amountFocusedListener, minAmountFocusedListener, volumeFocusedListener,
buyerSecurityDepositFocusedListener, priceFocusedListener, placeOfferCompletedListener,
priceAsPercentageFocusedListener, getShowWalletFundedNotificationListener,
tradeFeeInBtcToggleListener, tradeFeeInBsqToggleListener, tradeFeeVisibleListener;
tradeFeeInBtcToggleListener, tradeFeeInBsqToggleListener, tradeFeeVisibleListener,
isMinBuyerSecurityDepositListener;
private ChangeListener<String> tradeCurrencyCodeListener, errorMessageListener,
marketPriceMarginListener, volumeListener, buyerSecurityDepositInBTCListener;
private ChangeListener<Number> marketPriceAvailableListener;
Expand Down Expand Up @@ -825,6 +827,18 @@ private void createListeners() {
tradeFeeInBsqToggle.setVisible(newValue);
}
};

isMinBuyerSecurityDepositListener = ((observable, oldValue, newValue) -> {
if (newValue) {
// show BTC
buyerSecurityDepositPercentageLabel.setText(Res.getBaseCurrencyCode());
buyerSecurityDepositInputTextField.setDisable(true);
} else {
// show %
buyerSecurityDepositPercentageLabel.setText("%");
buyerSecurityDepositInputTextField.setDisable(false);
}
});
}

private void setIsCurrencyForMakerFeeBtc(boolean isCurrencyForMakerFeeBtc) {
Expand Down Expand Up @@ -862,6 +876,7 @@ private void addListeners() {
model.volume.addListener(volumeListener);
model.isTradeFeeVisible.addListener(tradeFeeVisibleListener);
model.buyerSecurityDepositInBTC.addListener(buyerSecurityDepositInBTCListener);
model.isMinBuyerSecurityDeposit.addListener(isMinBuyerSecurityDepositListener);

tradeFeeInBtcToggle.selectedProperty().addListener(tradeFeeInBtcToggleListener);
tradeFeeInBsqToggle.selectedProperty().addListener(tradeFeeInBsqToggleListener);
Expand Down Expand Up @@ -897,6 +912,7 @@ private void removeListeners() {
model.buyerSecurityDepositInBTC.removeListener(buyerSecurityDepositInBTCListener);
tradeFeeInBtcToggle.selectedProperty().removeListener(tradeFeeInBtcToggleListener);
tradeFeeInBsqToggle.selectedProperty().removeListener(tradeFeeInBsqToggleListener);
model.isMinBuyerSecurityDeposit.removeListener(isMinBuyerSecurityDepositListener);

// focus out
amountTextField.focusedProperty().removeListener(amountFocusedListener);
Expand Down Expand Up @@ -1105,7 +1121,7 @@ private VBox getBuyerSecurityDepositBox() {
Res.get("createOffer.securityDeposit.prompt"));
buyerSecurityDepositInfoInputTextField = tuple.second;
buyerSecurityDepositInputTextField = buyerSecurityDepositInfoInputTextField.getInputTextField();
Label buyerSecurityDepositPercentageLabel = tuple.third;
buyerSecurityDepositPercentageLabel = tuple.third;
// getEditableValueBox delivers BTC, so we overwrite it with %
buyerSecurityDepositPercentageLabel.setText("%");

Expand Down
Expand Up @@ -48,6 +48,7 @@
import bisq.core.offer.OfferRestrictions;
import bisq.core.offer.OfferUtil;
import bisq.core.payment.PaymentAccount;
import bisq.core.provider.fee.FeeService;
import bisq.core.provider.price.MarketPrice;
import bisq.core.provider.price.PriceFeedService;
import bisq.core.user.Preferences;
Expand Down Expand Up @@ -147,6 +148,7 @@ public abstract class MutableOfferViewModel<M extends MutableOfferDataModel> ext
final BooleanProperty showPayFundsScreenDisplayed = new SimpleBooleanProperty();
private final BooleanProperty showTransactionPublishedScreen = new SimpleBooleanProperty();
final BooleanProperty isWaitingForFunds = new SimpleBooleanProperty();
final BooleanProperty isMinBuyerSecurityDeposit = new SimpleBooleanProperty();

final ObjectProperty<InputValidator.ValidationResult> amountValidationResult = new SimpleObjectProperty<>();
final ObjectProperty<InputValidator.ValidationResult> minAmountValidationResult = new SimpleObjectProperty<>();
Expand Down Expand Up @@ -299,6 +301,7 @@ private void createListeners() {
dataModel.calculateVolume();
dataModel.calculateTotalToPay();
}
updateBuyerSecurityDeposit();
updateButtonDisableState();
}
};
Expand Down Expand Up @@ -970,7 +973,9 @@ public String getSecurityDepositPopOverLabel(String depositInBTC) {

public String getSecurityDepositInfo() {
return btcFormatter.formatCoinWithCode(dataModel.getSecurityDeposit()) +
GUIUtil.getPercentageOfTradeAmount(dataModel.getSecurityDeposit(), dataModel.getAmount().get());
GUIUtil.getPercentageOfTradeAmount(dataModel.getSecurityDeposit(),
dataModel.getAmount().get(),
Restrictions.getMinBuyerSecurityDepositAsCoin());
}

public String getSecurityDepositWithCode() {
Expand All @@ -982,7 +987,8 @@ public String getTradeFee() {
final Coin makerFeeAsCoin = dataModel.getMakerFee();
final String makerFee = getFormatterForMakerFee().formatCoinWithCode(makerFeeAsCoin);
if (dataModel.isCurrencyForMakerFeeBtc())
return makerFee + GUIUtil.getPercentageOfTradeAmount(makerFeeAsCoin, dataModel.getAmount().get());
return makerFee + GUIUtil.getPercentageOfTradeAmount(makerFeeAsCoin, dataModel.getAmount().get(),
FeeService.getMinMakerFee(dataModel.isCurrencyForMakerFeeBtc()));
else
return makerFee + " (" + Res.get("shared.tradingFeeInBsqInfo", btcFormatter.formatCoinWithCode(makerFeeAsCoin)) + ")";
}
Expand Down Expand Up @@ -1018,7 +1024,7 @@ public String getFundsStructure() {
public String getTxFee() {
Coin txFeeAsCoin = dataModel.getTxFee();
return btcFormatter.formatCoinWithCode(txFeeAsCoin) +
GUIUtil.getPercentageOfTradeAmount(txFeeAsCoin, dataModel.getAmount().get());
GUIUtil.getPercentageOfTradeAmount(txFeeAsCoin, dataModel.getAmount().get(), Coin.ZERO);

}

Expand Down Expand Up @@ -1196,18 +1202,34 @@ private void updateSpinnerInfo() {
isWaitingForFunds.set(!waitingForFundsText.get().isEmpty());
}

private void updateBuyerSecurityDeposit() {
isMinBuyerSecurityDeposit.set(dataModel.isMinBuyerSecurityDeposit());

if (dataModel.isMinBuyerSecurityDeposit()) {
buyerSecurityDepositLabel.set(Res.get("createOffer.minSecurityDepositUsed"));
buyerSecurityDeposit.set(btcFormatter.formatCoin(Restrictions.getMinBuyerSecurityDepositAsCoin()));
} else {
buyerSecurityDepositLabel.set(getSecurityDepositLabel());
buyerSecurityDeposit.set(FormattingUtils.formatToPercent(dataModel.getBuyerSecurityDeposit().get()));
}
}

private void updateButtonDisableState() {
log.debug("updateButtonDisableState");
boolean inputDataValid = isBtcInputValid(amount.get()).isValid &&
isBtcInputValid(minAmount.get()).isValid &&
isPriceInputValid(price.get()).isValid &&
securityDepositValidator.validate(buyerSecurityDeposit.get()).isValid &&
dataModel.getPrice().get() != null &&
dataModel.getPrice().get().getValue() != 0 &&
isVolumeInputValid(volume.get()).isValid &&
isVolumeInputValid(DisplayUtils.formatVolume(dataModel.getMinVolume().get())).isValid &&
dataModel.isMinAmountLessOrEqualAmount();

// validating the percentage deposit value only makes sense if it is actually used
if (!dataModel.isMinBuyerSecurityDeposit()) {
inputDataValid = inputDataValid && securityDepositValidator.validate(buyerSecurityDeposit.get()).isValid;
}

isNextButtonDisabled.set(!inputDataValid);
// boolean notSufficientFees = dataModel.isWalletFunded.get() && dataModel.isMainNet.get() && !dataModel.isFeeFromFundingTxSufficient.get();
//isPlaceOfferButtonDisabled.set(createOfferRequested || !inputDataValid || notSufficientFees);
Expand Down
Expand Up @@ -29,6 +29,7 @@
import bisq.desktop.util.validation.BtcValidator;

import bisq.core.account.witness.AccountAgeWitnessService;
import bisq.core.btc.wallet.Restrictions;
import bisq.core.locale.CurrencyUtil;
import bisq.core.locale.Res;
import bisq.core.monetary.Price;
Expand All @@ -39,6 +40,7 @@
import bisq.core.offer.OfferUtil;
import bisq.core.payment.PaymentAccount;
import bisq.core.payment.payload.PaymentMethod;
import bisq.core.provider.fee.FeeService;
import bisq.core.provider.price.PriceFeedService;
import bisq.core.trade.Trade;
import bisq.core.user.Preferences;
Expand Down Expand Up @@ -707,7 +709,9 @@ String getTradeAmount() {

public String getSecurityDepositInfo() {
return btcFormatter.formatCoinWithCode(dataModel.getSecurityDeposit()) +
GUIUtil.getPercentageOfTradeAmount(dataModel.getSecurityDeposit(), dataModel.getAmount().get());
GUIUtil.getPercentageOfTradeAmount(dataModel.getSecurityDeposit(),
dataModel.getAmount().get(),
Restrictions.getMinBuyerSecurityDepositAsCoin());
}

public String getSecurityDepositWithCode() {
Expand All @@ -719,7 +723,8 @@ public String getTradeFee() {
final Coin takerFeeAsCoin = dataModel.getTakerFee();
final String takerFee = getFormatterForTakerFee().formatCoinWithCode(takerFeeAsCoin);
if (dataModel.isCurrencyForTakerFeeBtc())
return takerFee + GUIUtil.getPercentageOfTradeAmount(takerFeeAsCoin, dataModel.getAmount().get());
return takerFee + GUIUtil.getPercentageOfTradeAmount(takerFeeAsCoin, dataModel.getAmount().get(),
FeeService.getMinTakerFee(dataModel.isCurrencyForTakerFeeBtc()));
else
return takerFee + " (" + Res.get("shared.tradingFeeInBsqInfo", btcFormatter.formatCoinWithCode(takerFeeAsCoin)) + ")";
}
Expand All @@ -743,7 +748,8 @@ public String getTotalToPayInfo() {
public String getTxFee() {
Coin txFeeAsCoin = dataModel.getTotalTxFee();
return btcFormatter.formatCoinWithCode(txFeeAsCoin) +
GUIUtil.getPercentageOfTradeAmount(txFeeAsCoin, dataModel.getAmount().get());
GUIUtil.getPercentageOfTradeAmount(txFeeAsCoin, dataModel.getAmount().get(),
Coin.ZERO);

}

Expand Down
Expand Up @@ -24,10 +24,12 @@

import bisq.core.account.witness.AccountAgeWitness;
import bisq.core.account.witness.AccountAgeWitnessService;
import bisq.core.btc.wallet.Restrictions;
import bisq.core.locale.CurrencyUtil;
import bisq.core.locale.Res;
import bisq.core.network.MessageState;
import bisq.core.offer.Offer;
import bisq.core.provider.fee.FeeService;
import bisq.core.trade.Contract;
import bisq.core.trade.Trade;
import bisq.core.trade.closed.ClosedTradableManager;
Expand Down Expand Up @@ -291,7 +293,8 @@ public String getFiatVolume() {
public String getTxFee() {
if (trade != null && trade.getTradeAmount() != null) {
Coin txFee = dataModel.getTxFee();
String percentage = GUIUtil.getPercentageOfTradeAmount(txFee, trade.getTradeAmount());
String percentage = GUIUtil.getPercentageOfTradeAmount(txFee, trade.getTradeAmount(),
Coin.ZERO);
return btcFormatter.formatCoinWithCode(txFee) + percentage;
} else {
return "";
Expand All @@ -303,7 +306,13 @@ public String getTradeFee() {
if (dataModel.isMaker() && dataModel.getOffer().isCurrencyForMakerFeeBtc() ||
!dataModel.isMaker() && dataModel.getTrade().isCurrencyForTakerFeeBtc()) {
Coin tradeFeeInBTC = dataModel.getTradeFeeInBTC();
String percentage = GUIUtil.getPercentageOfTradeAmount(tradeFeeInBTC, trade.getTradeAmount());

Coin minTradeFee = dataModel.isMaker() ?
FeeService.getMinMakerFee(true) :
FeeService.getMinTakerFee(true);

String percentage = GUIUtil.getPercentageOfTradeAmount(tradeFeeInBTC, trade.getTradeAmount(),
minTradeFee);
return btcFormatter.formatCoinWithCode(tradeFeeInBTC) + percentage;
} else {
return bsqFormatter.formatCoinWithCode(dataModel.getTradeFeeAsBsq());
Expand All @@ -320,7 +329,14 @@ public String getSecurityDeposit() {
Coin securityDeposit = dataModel.isBuyer() ?
offer.getBuyerSecurityDeposit()
: offer.getSellerSecurityDeposit();
String percentage = GUIUtil.getPercentageOfTradeAmount(securityDeposit, trade.getTradeAmount());

Coin minSecurityDeposit = dataModel.isBuyer() ?
Restrictions.getMinBuyerSecurityDepositAsCoin() :
Restrictions.getMinSellerSecurityDepositAsCoin();

String percentage = GUIUtil.getPercentageOfTradeAmount(securityDeposit,
trade.getTradeAmount(),
minSecurityDeposit);
return btcFormatter.formatCoinWithCode(securityDeposit) + percentage;
} else {
return "";
Expand Down
12 changes: 9 additions & 3 deletions desktop/src/main/java/bisq/desktop/util/GUIUtil.java
Expand Up @@ -49,10 +49,10 @@
import bisq.core.user.DontShowAgainLookup;
import bisq.core.user.Preferences;
import bisq.core.user.User;
import bisq.core.util.FormattingUtils;
import bisq.core.util.coin.BsqFormatter;
import bisq.core.util.coin.CoinFormatter;
import bisq.core.util.coin.CoinUtil;
import bisq.core.util.FormattingUtils;

import bisq.network.p2p.P2PService;

Expand Down Expand Up @@ -662,9 +662,15 @@ private static void doOpenWebPage(String target) {
}
}

public static String getPercentageOfTradeAmount(Coin fee, Coin tradeAmount) {
return " (" + getPercentage(fee, tradeAmount) +
public static String getPercentageOfTradeAmount(Coin fee, Coin tradeAmount, Coin minFee) {
String result = " (" + getPercentage(fee, tradeAmount) +
" " + Res.get("guiUtil.ofTradeAmount") + ")";

if (!fee.isGreaterThan(minFee)) {
result = " " + Res.get("guiUtil.requiredMinimum");
}

return result;
}

public static String getPercentage(Coin part, Coin total) {
Expand Down
36 changes: 36 additions & 0 deletions desktop/src/test/java/bisq/desktop/util/GUIUtilTest.java
Expand Up @@ -23,6 +23,9 @@
import bisq.core.user.DontShowAgainLookup;
import bisq.core.user.Preferences;

import org.bitcoinj.core.Coin;
import org.bitcoinj.core.CoinMaker;

import javafx.util.StringConverter;

import java.util.HashMap;
Expand All @@ -35,6 +38,11 @@

import static bisq.desktop.maker.TradeCurrencyMakers.bitcoin;
import static bisq.desktop.maker.TradeCurrencyMakers.euro;
import static com.natpryce.makeiteasy.MakeItEasy.a;
import static com.natpryce.makeiteasy.MakeItEasy.make;
import static com.natpryce.makeiteasy.MakeItEasy.with;
import static org.bitcoinj.core.CoinMaker.oneBitcoin;
import static org.bitcoinj.core.CoinMaker.satoshis;
import static org.junit.Assert.assertEquals;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
Expand Down Expand Up @@ -103,4 +111,32 @@ public void testOpenURLWithoutCampaignParameters() throws Exception {
assertEquals("https://www.github.com", captor.getValue().toString());
*/
}

@Test
public void percentageOfTradeAmount_higherFeeAsMin() {

Coin fee = make(a(CoinMaker.Coin).but(with(satoshis, 20000L)));
Coin min = make(a(CoinMaker.Coin).but(with(satoshis, 10000L)));

assertEquals(" (0.02% of trade amount)", GUIUtil.getPercentageOfTradeAmount(fee, oneBitcoin, min));
}

@Test
public void percentageOfTradeAmount_minFee() {

Coin fee = make(a(CoinMaker.Coin).but(with(satoshis, 10000L)));
Coin min = make(a(CoinMaker.Coin).but(with(satoshis, 10000L)));

assertEquals(" (required minimum)",
GUIUtil.getPercentageOfTradeAmount(fee, oneBitcoin, min));
}

@Test
public void percentageOfTradeAmount_minFeeZERO() {

Coin fee = make(a(CoinMaker.Coin).but(with(satoshis, 10000L)));

assertEquals(" (0.01% of trade amount)",
GUIUtil.getPercentageOfTradeAmount(fee, oneBitcoin, Coin.ZERO));
}
}