mirror of
https://github.com/bisq-network/bisq.git
synced 2024-11-20 10:22:18 +01:00
Make security deposit customizable at offer creation (allowed range: 0.001 BTC - 0.2 BTC).
Use new max. trade limits
This commit is contained in:
parent
159b208273
commit
827c581a73
@ -46,7 +46,6 @@ public class BitcoinModule extends AppModule {
|
||||
@Override
|
||||
protected void configure() {
|
||||
bind(RegTestHost.class).toInstance(env.getProperty(BtcOptionKeys.REG_TEST_HOST, RegTestHost.class, RegTestHost.DEFAULT));
|
||||
bind(FeePolicy.class).in(Singleton.class);
|
||||
|
||||
bindConstant().annotatedWith(named(UserAgent.NAME_KEY)).to(env.getRequiredProperty(UserAgent.NAME_KEY));
|
||||
bindConstant().annotatedWith(named(UserAgent.VERSION_KEY)).to(env.getRequiredProperty(UserAgent.VERSION_KEY));
|
||||
|
@ -1,49 +0,0 @@
|
||||
/*
|
||||
* This file is part of Bitsquare.
|
||||
*
|
||||
* Bitsquare is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or (at
|
||||
* your option) any later version.
|
||||
*
|
||||
* Bitsquare is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
|
||||
* License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Bitsquare. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package io.bitsquare.btc;
|
||||
|
||||
import org.bitcoinj.core.Coin;
|
||||
|
||||
public class FeePolicy {
|
||||
|
||||
// With block getting filled up the needed fee to get fast into a black has become more expensive and less predictable.
|
||||
// To see current fees check out:
|
||||
// https://tradeblock.com/blockchain
|
||||
// https://jochen-hoenicke.de/queue/24h.html
|
||||
// https://bitcoinfees.21.co/
|
||||
// http://p2sh.info/dashboard/db/fee-estimation
|
||||
// https://bitcoinfees.github.io/#1d
|
||||
// https://estimatefee.appspot.com/
|
||||
// Average values are 10-100 satoshis/byte in january 2016
|
||||
// Average values are 60-140 satoshis/byte in february 2017
|
||||
|
||||
private static Coin NON_TRADE_FEE_PER_KB = Coin.valueOf(150_000);
|
||||
|
||||
public static void setNonTradeFeePerKb(Coin nonTradeFeePerKb) {
|
||||
NON_TRADE_FEE_PER_KB = nonTradeFeePerKb;
|
||||
}
|
||||
|
||||
public static Coin getNonTradeFeePerKb() {
|
||||
return NON_TRADE_FEE_PER_KB;
|
||||
}
|
||||
|
||||
public static Coin getDefaultSecurityDeposit() {
|
||||
return Coin.valueOf(3_000_000);
|
||||
}
|
||||
|
||||
}
|
@ -24,6 +24,10 @@ public class Restrictions {
|
||||
|
||||
public static final Coin MIN_TRADE_AMOUNT = Coin.parseCoin("0.0001"); // 4 cent @ 400 EUR/BTC
|
||||
|
||||
public static final Coin MAX_SECURITY_DEPOSIT = Coin.parseCoin("0.2");
|
||||
public static final Coin MIN_SECURITY_DEPOSIT = Coin.parseCoin("0.001");
|
||||
public static final Coin DEFAULT_SECURITY_DEPOSIT = Coin.parseCoin("0.03");
|
||||
|
||||
public static boolean isAboveDust(Coin amount, Coin txFee) {
|
||||
return amount != null && amount.compareTo(txFee.add(Transaction.MIN_NONDUST_OUTPUT)) >= 0;
|
||||
}
|
||||
|
@ -75,37 +75,37 @@ public final class PaymentMethod implements Persistable, Comparable {
|
||||
|
||||
public static final List<PaymentMethod> ALL_VALUES = new ArrayList<>(Arrays.asList(
|
||||
// EUR
|
||||
SEPA = new PaymentMethod(SEPA_ID, 0, 8 * DAY, Coin.parseCoin("1")), // sepa takes 1-3 business days. We use 8 days to include safety for holidays
|
||||
SEPA = new PaymentMethod(SEPA_ID, 0, 8 * DAY, Coin.parseCoin("0.5")), // sepa takes 1-3 business days. We use 8 days to include safety for holidays
|
||||
|
||||
// Global
|
||||
NATIONAL_BANK = new PaymentMethod(NATIONAL_BANK_ID, 0, 4 * DAY, Coin.parseCoin("1")),
|
||||
SAME_BANK = new PaymentMethod(SAME_BANK_ID, 0, 2 * DAY, Coin.parseCoin("1")),
|
||||
SPECIFIC_BANKS = new PaymentMethod(SPECIFIC_BANKS_ID, 0, 4 * DAY, Coin.parseCoin("1")),
|
||||
CASH_DEPOSIT = new PaymentMethod(CASH_DEPOSIT_ID, 0, 4 * DAY, Coin.parseCoin("1")),
|
||||
NATIONAL_BANK = new PaymentMethod(NATIONAL_BANK_ID, 0, 4 * DAY, Coin.parseCoin("0.5")),
|
||||
SAME_BANK = new PaymentMethod(SAME_BANK_ID, 0, 2 * DAY, Coin.parseCoin("0.5")),
|
||||
SPECIFIC_BANKS = new PaymentMethod(SPECIFIC_BANKS_ID, 0, 4 * DAY, Coin.parseCoin("0.5")),
|
||||
CASH_DEPOSIT = new PaymentMethod(CASH_DEPOSIT_ID, 0, 4 * DAY, Coin.parseCoin("0.5")),
|
||||
|
||||
// Trans national
|
||||
OK_PAY = new PaymentMethod(OK_PAY_ID, 0, DAY, Coin.parseCoin("2")),
|
||||
OK_PAY = new PaymentMethod(OK_PAY_ID, 0, DAY, Coin.parseCoin("1")),
|
||||
PERFECT_MONEY = new PaymentMethod(PERFECT_MONEY_ID, 0, DAY, Coin.parseCoin("1")),
|
||||
|
||||
// UK
|
||||
FASTER_PAYMENTS = new PaymentMethod(FASTER_PAYMENTS_ID, 0, DAY, Coin.parseCoin("1")),
|
||||
FASTER_PAYMENTS = new PaymentMethod(FASTER_PAYMENTS_ID, 0, DAY, Coin.parseCoin("0.5")),
|
||||
|
||||
// Canada
|
||||
INTERAC_E_TRANSFER = new PaymentMethod(INTERAC_E_TRANSFER_ID, 0, DAY, Coin.parseCoin("1")),
|
||||
INTERAC_E_TRANSFER = new PaymentMethod(INTERAC_E_TRANSFER_ID, 0, DAY, Coin.parseCoin("0.5")),
|
||||
|
||||
// US
|
||||
CLEAR_X_CHANGE = new PaymentMethod(CLEAR_X_CHANGE_ID, 0, 4 * DAY, Coin.parseCoin("1")),
|
||||
CHASE_QUICK_PAY = new PaymentMethod(CHASE_QUICK_PAY_ID, 0, DAY, Coin.parseCoin("1")),
|
||||
US_POSTAL_MONEY_ORDER = new PaymentMethod(US_POSTAL_MONEY_ORDER_ID, 0, 8 * DAY, Coin.parseCoin("1")),
|
||||
CLEAR_X_CHANGE = new PaymentMethod(CLEAR_X_CHANGE_ID, 0, 4 * DAY, Coin.parseCoin("0.5")),
|
||||
CHASE_QUICK_PAY = new PaymentMethod(CHASE_QUICK_PAY_ID, 0, DAY, Coin.parseCoin("0.5")),
|
||||
US_POSTAL_MONEY_ORDER = new PaymentMethod(US_POSTAL_MONEY_ORDER_ID, 0, 8 * DAY, Coin.parseCoin("0.5")),
|
||||
|
||||
// Sweden
|
||||
SWISH = new PaymentMethod(SWISH_ID, 0, DAY, Coin.parseCoin("2")),
|
||||
SWISH = new PaymentMethod(SWISH_ID, 0, DAY, Coin.parseCoin("1")),
|
||||
|
||||
// China
|
||||
ALI_PAY = new PaymentMethod(ALI_PAY_ID, 0, DAY, Coin.parseCoin("2")),
|
||||
ALI_PAY = new PaymentMethod(ALI_PAY_ID, 0, DAY, Coin.parseCoin("1")),
|
||||
|
||||
// Altcoins
|
||||
BLOCK_CHAINS = new PaymentMethod(BLOCK_CHAINS_ID, 0, DAY, Coin.parseCoin("3"))
|
||||
BLOCK_CHAINS = new PaymentMethod(BLOCK_CHAINS_ID, 0, DAY, Coin.parseCoin("1"))
|
||||
));
|
||||
|
||||
private final String id;
|
||||
|
@ -22,6 +22,7 @@ import io.bitsquare.app.BitsquareEnvironment;
|
||||
import io.bitsquare.app.DevFlags;
|
||||
import io.bitsquare.app.Version;
|
||||
import io.bitsquare.btc.BitcoinNetwork;
|
||||
import io.bitsquare.btc.Restrictions;
|
||||
import io.bitsquare.btc.provider.fee.FeeService;
|
||||
import io.bitsquare.common.persistance.Persistable;
|
||||
import io.bitsquare.common.util.Utilities;
|
||||
@ -31,6 +32,7 @@ import javafx.beans.property.*;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.collections.ListChangeListener;
|
||||
import javafx.collections.ObservableList;
|
||||
import org.bitcoinj.core.Coin;
|
||||
import org.bitcoinj.utils.MonetaryFormat;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
@ -135,6 +137,7 @@ public final class Preferences implements Persistable {
|
||||
|
||||
private List<String> ignoreTradersList = new ArrayList<>();
|
||||
private String defaultPath;
|
||||
private long securityDepositAsLong = Restrictions.DEFAULT_SECURITY_DEPOSIT.value;
|
||||
|
||||
// Observable wrappers
|
||||
transient private final StringProperty btcDenominationProperty = new SimpleStringProperty(btcDenomination);
|
||||
@ -256,6 +259,8 @@ public final class Preferences implements Persistable {
|
||||
|
||||
if (persisted.getDefaultPath() != null)
|
||||
defaultPath = persisted.getDefaultPath();
|
||||
|
||||
securityDepositAsLong = persisted.getSecurityDepositAsLong();
|
||||
} else {
|
||||
setFiatCurrencies(CurrencyUtil.getAllMainFiatCurrencies());
|
||||
setCryptoCurrencies(CurrencyUtil.getMainCryptoCurrencies());
|
||||
@ -473,6 +478,12 @@ public final class Preferences implements Persistable {
|
||||
withdrawalTxFeeInBytesProperty.set(withdrawalTxFeeInBytes);
|
||||
}
|
||||
|
||||
public void setSecurityDepositAsLong(long securityDepositAsLong) {
|
||||
this.securityDepositAsLong = securityDepositAsLong;
|
||||
storage.queueUpForSave();
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Getter
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
@ -640,6 +651,14 @@ public final class Preferences implements Persistable {
|
||||
return withdrawalTxFeeInBytesProperty.get();
|
||||
}
|
||||
|
||||
public long getSecurityDepositAsLong() {
|
||||
return securityDepositAsLong;
|
||||
}
|
||||
|
||||
public Coin getSecurityDepositAsCoin() {
|
||||
return Coin.valueOf(securityDepositAsLong);
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Private
|
||||
|
@ -22,7 +22,7 @@ import io.bitsquare.app.DevFlags;
|
||||
import io.bitsquare.app.Version;
|
||||
import io.bitsquare.arbitration.Arbitrator;
|
||||
import io.bitsquare.btc.AddressEntry;
|
||||
import io.bitsquare.btc.FeePolicy;
|
||||
import io.bitsquare.btc.Restrictions;
|
||||
import io.bitsquare.btc.listeners.BalanceListener;
|
||||
import io.bitsquare.btc.provider.fee.FeeService;
|
||||
import io.bitsquare.btc.provider.price.PriceFeedService;
|
||||
@ -55,6 +55,7 @@ import org.bitcoinj.core.Transaction;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkArgument;
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
|
||||
/**
|
||||
@ -79,7 +80,6 @@ class CreateOfferDataModel extends ActivatableDataModel {
|
||||
private final AddressEntry addressEntry;
|
||||
private Coin createOfferFeeAsCoin;
|
||||
private Coin txFeeAsCoin;
|
||||
private final Coin securityDepositAsCoin;
|
||||
private final BalanceListener balanceListener;
|
||||
private final SetChangeListener<PaymentAccount> paymentAccountsChangeListener;
|
||||
|
||||
@ -102,6 +102,7 @@ class CreateOfferDataModel extends ActivatableDataModel {
|
||||
// If we would change the price representation in the domain we would not be backward compatible
|
||||
final ObjectProperty<Price> price = new SimpleObjectProperty<>();
|
||||
final ObjectProperty<Volume> volume = new SimpleObjectProperty<>();
|
||||
final ObjectProperty<Coin> securityDeposit = new SimpleObjectProperty<>();
|
||||
final ObjectProperty<Coin> totalToPayAsCoin = new SimpleObjectProperty<>();
|
||||
final ObjectProperty<Coin> missingCoin = new SimpleObjectProperty<>(Coin.ZERO);
|
||||
final ObjectProperty<Coin> balance = new SimpleObjectProperty<>();
|
||||
@ -143,9 +144,7 @@ class CreateOfferDataModel extends ActivatableDataModel {
|
||||
addressEntry = walletService.getOrCreateAddressEntry(offerId, AddressEntry.Context.OFFER_FUNDING);
|
||||
|
||||
useMarketBasedPrice.set(preferences.getUsePercentageBasedPrice());
|
||||
|
||||
// TODO add ui for editing, use preferences
|
||||
securityDepositAsCoin = FeePolicy.getDefaultSecurityDeposit();
|
||||
securityDeposit.set(preferences.getSecurityDepositAsCoin());
|
||||
|
||||
balanceListener = new BalanceListener(getAddressEntry().getAddress()) {
|
||||
@Override
|
||||
@ -327,6 +326,11 @@ class CreateOfferDataModel extends ActivatableDataModel {
|
||||
long lowerClosePrice = 0;
|
||||
long upperClosePrice = 0;
|
||||
|
||||
Coin securityDepositAsCoin = securityDeposit.get();
|
||||
checkArgument(securityDepositAsCoin.compareTo(Restrictions.MAX_SECURITY_DEPOSIT) <= 0, "securityDeposit must be not exceed " +
|
||||
Restrictions.MAX_SECURITY_DEPOSIT.toFriendlyString());
|
||||
checkArgument(securityDepositAsCoin.compareTo(Restrictions.MIN_SECURITY_DEPOSIT) >= 0, "securityDeposit must be not be less than " +
|
||||
Restrictions.MIN_SECURITY_DEPOSIT.toFriendlyString());
|
||||
return new Offer(offerId,
|
||||
p2PService.getAddress(),
|
||||
keyRing.getPubKeyRing(),
|
||||
@ -520,7 +524,7 @@ class CreateOfferDataModel extends ActivatableDataModel {
|
||||
// created the offer and reserved his funds, so that would not work well with dynamic fees.
|
||||
// The mining fee for the createOfferFee tx is deducted from the createOfferFee and not visible to the trader
|
||||
if (direction != null && amount.get() != null && createOfferFeeAsCoin != null) {
|
||||
Coin feeAndSecDeposit = createOfferFeeAsCoin.add(txFeeAsCoin).add(securityDepositAsCoin);
|
||||
Coin feeAndSecDeposit = createOfferFeeAsCoin.add(txFeeAsCoin).add(securityDeposit.get());
|
||||
Coin required = direction == Offer.Direction.BUY ? feeAndSecDeposit : feeAndSecDeposit.add(amount.get());
|
||||
totalToPayAsCoin.set(required);
|
||||
log.debug("totalToPayAsCoin " + totalToPayAsCoin.get().toFriendlyString());
|
||||
@ -576,8 +580,8 @@ class CreateOfferDataModel extends ActivatableDataModel {
|
||||
return txFeeAsCoin;
|
||||
}
|
||||
|
||||
public Coin getSecurityDepositAsCoin() {
|
||||
return securityDepositAsCoin;
|
||||
public Coin getSecurityDeposit() {
|
||||
return securityDeposit.get();
|
||||
}
|
||||
|
||||
public List<Arbitrator> getArbitrators() {
|
||||
@ -610,6 +614,11 @@ class CreateOfferDataModel extends ActivatableDataModel {
|
||||
this.amount.set(amount);
|
||||
}
|
||||
|
||||
void setSecurityDeposit(Coin securityDeposit) {
|
||||
this.securityDeposit.set(securityDeposit);
|
||||
preferences.setSecurityDepositAsLong(securityDeposit.value);
|
||||
}
|
||||
|
||||
void updateTradeFee() {
|
||||
createOfferFeeAsCoin = Utilities.getFeePerBtc(feeService.getCreateOfferFeeInBtcPerBtc(), amount.get());
|
||||
// We don't want too fractional btc values so we use only a divide by 10 instead of 100
|
||||
|
@ -111,6 +111,8 @@ public class CreateOfferView extends ActivatableViewAndModel<AnchorPane, CreateO
|
||||
private ChangeListener<Boolean> minAmountFocusedListener;
|
||||
private ChangeListener<Boolean> priceFocusedListener, priceAsPercentageFocusedListener;
|
||||
private ChangeListener<Boolean> volumeFocusedListener;
|
||||
private ChangeListener<Boolean> securityDepositFocusedListener;
|
||||
|
||||
private ChangeListener<String> errorMessageListener;
|
||||
private ChangeListener<Boolean> placeOfferCompletedListener;
|
||||
// private ChangeListener<Coin> feeFromFundingTxListener;
|
||||
@ -135,6 +137,10 @@ public class CreateOfferView extends ActivatableViewAndModel<AnchorPane, CreateO
|
||||
private HBox firstRowHBox;
|
||||
private HBox toggleButtonsHBox;
|
||||
private ChangeListener<Number> marketPriceAvailableListener;
|
||||
private InputTextField securityDepositTextField;
|
||||
private Label securityDepositBtcLabel;
|
||||
private Label securityDepositDescriptionLabel;
|
||||
private HBox thirdRowHBox;
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
@ -210,6 +216,8 @@ public class CreateOfferView extends ActivatableViewAndModel<AnchorPane, CreateO
|
||||
|
||||
// if (DevFlags.STRESS_TEST_MODE)
|
||||
// UserThread.runAfter(this::onShowPayFundsScreen, 200, TimeUnit.MILLISECONDS);
|
||||
|
||||
updateMarketPriceAvailable();
|
||||
}
|
||||
}
|
||||
|
||||
@ -347,7 +355,7 @@ public class CreateOfferView extends ActivatableViewAndModel<AnchorPane, CreateO
|
||||
|
||||
"The amount is the sum of:\n" +
|
||||
tradeAmountText +
|
||||
"- Security deposit: " + model.getSecurityDeposit() + "\n" +
|
||||
"- Security deposit: " + model.getSecurityDepositInfo() + "\n" +
|
||||
"- Trading fee: " + model.getCreateOfferFee() + "\n" +
|
||||
"- Mining fee: " + model.getTxFee() + "\n\n" +
|
||||
|
||||
@ -439,6 +447,7 @@ public class CreateOfferView extends ActivatableViewAndModel<AnchorPane, CreateO
|
||||
|
||||
private void addBindings() {
|
||||
amountBtcLabel.textProperty().bind(model.btcCode);
|
||||
securityDepositBtcLabel.textProperty().bind(model.btcCode);
|
||||
priceCurrencyLabel.textProperty().bind(createStringBinding(() -> formatter.getCounterCurrency(model.tradeCurrencyCode.get()), model.btcCode, model.tradeCurrencyCode));
|
||||
|
||||
marketBasedPriceLabel.prefWidthProperty().bind(priceCurrencyLabel.widthProperty());
|
||||
@ -464,12 +473,14 @@ public class CreateOfferView extends ActivatableViewAndModel<AnchorPane, CreateO
|
||||
volumeTextField.promptTextProperty().bind(model.volumePromptLabel);
|
||||
totalToPayTextField.textProperty().bind(model.totalToPay);
|
||||
addressTextField.amountAsCoinProperty().bind(model.dataModel.missingCoin);
|
||||
securityDepositTextField.textProperty().bindBidirectional(model.securityDeposit);
|
||||
|
||||
// Validation
|
||||
amountTextField.validationResultProperty().bind(model.amountValidationResult);
|
||||
minAmountTextField.validationResultProperty().bind(model.minAmountValidationResult);
|
||||
fixedPriceTextField.validationResultProperty().bind(model.priceValidationResult);
|
||||
volumeTextField.validationResultProperty().bind(model.volumeValidationResult);
|
||||
securityDepositTextField.validationResultProperty().bind(model.securityDepositValidationResult);
|
||||
|
||||
// funding
|
||||
fundingHBox.visibleProperty().bind(model.dataModel.isWalletFunded.not().and(model.showPayFundsScreenDisplayed));
|
||||
@ -493,6 +504,7 @@ public class CreateOfferView extends ActivatableViewAndModel<AnchorPane, CreateO
|
||||
|
||||
private void removeBindings() {
|
||||
amountBtcLabel.textProperty().unbind();
|
||||
securityDepositBtcLabel.textProperty().unbind();
|
||||
priceCurrencyLabel.textProperty().unbind();
|
||||
fixedPriceTextField.disableProperty().unbind();
|
||||
priceCurrencyLabel.disableProperty().unbind();
|
||||
@ -512,12 +524,14 @@ public class CreateOfferView extends ActivatableViewAndModel<AnchorPane, CreateO
|
||||
volumeTextField.promptTextProperty().unbindBidirectional(model.volume);
|
||||
totalToPayTextField.textProperty().unbind();
|
||||
addressTextField.amountAsCoinProperty().unbind();
|
||||
securityDepositTextField.textProperty().unbindBidirectional(model.securityDeposit);
|
||||
|
||||
// Validation
|
||||
amountTextField.validationResultProperty().unbind();
|
||||
minAmountTextField.validationResultProperty().unbind();
|
||||
fixedPriceTextField.validationResultProperty().unbind();
|
||||
volumeTextField.validationResultProperty().unbind();
|
||||
securityDepositTextField.validationResultProperty().unbind();
|
||||
|
||||
// funding
|
||||
fundingHBox.visibleProperty().unbind();
|
||||
@ -563,7 +577,6 @@ public class CreateOfferView extends ActivatableViewAndModel<AnchorPane, CreateO
|
||||
model.onFocusOutAmountTextField(oldValue, newValue, amountTextField.getText());
|
||||
amountTextField.setText(model.amount.get());
|
||||
};
|
||||
|
||||
minAmountFocusedListener = (o, oldValue, newValue) -> {
|
||||
model.onFocusOutMinAmountTextField(oldValue, newValue, minAmountTextField.getText());
|
||||
minAmountTextField.setText(model.minAmount.get());
|
||||
@ -580,6 +593,10 @@ public class CreateOfferView extends ActivatableViewAndModel<AnchorPane, CreateO
|
||||
model.onFocusOutVolumeTextField(oldValue, newValue, volumeTextField.getText());
|
||||
volumeTextField.setText(model.volume.get());
|
||||
};
|
||||
securityDepositFocusedListener = (o, oldValue, newValue) -> {
|
||||
model.onFocusOutSecurityDepositTextField(oldValue, newValue, securityDepositTextField.getText());
|
||||
securityDepositTextField.setText(model.securityDeposit.get());
|
||||
};
|
||||
errorMessageListener = (o, oldValue, newValue) -> {
|
||||
if (newValue != null)
|
||||
UserThread.runAfter(() -> new Popup().error(BSResources.get("createOffer.amountPriceBox.error.message", model.errorMessage.get()) +
|
||||
@ -648,18 +665,23 @@ public class CreateOfferView extends ActivatableViewAndModel<AnchorPane, CreateO
|
||||
|
||||
|
||||
marketPriceAvailableListener = (observable, oldValue, newValue) -> {
|
||||
if (newValue.intValue() > -1) {
|
||||
boolean isMarketPriceAvailable = newValue.intValue() == 1;
|
||||
percentagePriceBox.setVisible(isMarketPriceAvailable);
|
||||
percentagePriceBox.setManaged(isMarketPriceAvailable);
|
||||
toggleButtonsHBox.setVisible(isMarketPriceAvailable);
|
||||
toggleButtonsHBox.setManaged(isMarketPriceAvailable);
|
||||
boolean fixedPriceSelected = !model.dataModel.useMarketBasedPrice.get() || !isMarketPriceAvailable;
|
||||
updateToggleButtons(fixedPriceSelected);
|
||||
}
|
||||
updateMarketPriceAvailable();
|
||||
};
|
||||
}
|
||||
|
||||
private void updateMarketPriceAvailable() {
|
||||
int marketPriceAvailableValue = model.marketPriceAvailableProperty.get();
|
||||
if (marketPriceAvailableValue > -1) {
|
||||
boolean isMarketPriceAvailable = marketPriceAvailableValue == 1;
|
||||
percentagePriceBox.setVisible(isMarketPriceAvailable);
|
||||
percentagePriceBox.setManaged(isMarketPriceAvailable);
|
||||
toggleButtonsHBox.setVisible(isMarketPriceAvailable);
|
||||
toggleButtonsHBox.setManaged(isMarketPriceAvailable);
|
||||
boolean fixedPriceSelected = !model.dataModel.useMarketBasedPrice.get() || !isMarketPriceAvailable;
|
||||
updateToggleButtons(fixedPriceSelected);
|
||||
}
|
||||
}
|
||||
|
||||
private void addListeners() {
|
||||
model.tradeCurrencyCode.addListener(tradeCurrencyCodeListener);
|
||||
model.marketPriceAvailableProperty.addListener(marketPriceAvailableListener);
|
||||
@ -670,7 +692,8 @@ public class CreateOfferView extends ActivatableViewAndModel<AnchorPane, CreateO
|
||||
fixedPriceTextField.focusedProperty().addListener(priceFocusedListener);
|
||||
marketBasedPriceTextField.focusedProperty().addListener(priceAsPercentageFocusedListener);
|
||||
volumeTextField.focusedProperty().addListener(volumeFocusedListener);
|
||||
|
||||
securityDepositTextField.focusedProperty().addListener(securityDepositFocusedListener);
|
||||
|
||||
// warnings
|
||||
model.errorMessage.addListener(errorMessageListener);
|
||||
// model.dataModel.feeFromFundingTxProperty.addListener(feeFromFundingTxListener);
|
||||
@ -692,6 +715,7 @@ public class CreateOfferView extends ActivatableViewAndModel<AnchorPane, CreateO
|
||||
fixedPriceTextField.focusedProperty().removeListener(priceFocusedListener);
|
||||
marketBasedPriceTextField.focusedProperty().removeListener(priceAsPercentageFocusedListener);
|
||||
volumeTextField.focusedProperty().removeListener(volumeFocusedListener);
|
||||
securityDepositTextField.focusedProperty().removeListener(securityDepositFocusedListener);
|
||||
|
||||
// warnings
|
||||
model.errorMessage.removeListener(errorMessageListener);
|
||||
@ -776,7 +800,7 @@ public class CreateOfferView extends ActivatableViewAndModel<AnchorPane, CreateO
|
||||
}
|
||||
|
||||
private void addAmountPriceGroup() {
|
||||
TitledGroupBg titledGroupBg = addTitledGroupBg(gridPane, ++gridRow, 2, "Set amount and price", Layout.GROUP_DISTANCE);
|
||||
TitledGroupBg titledGroupBg = addTitledGroupBg(gridPane, ++gridRow, 3, "Set amount and price", Layout.GROUP_DISTANCE);
|
||||
GridPane.setColumnSpan(titledGroupBg, 3);
|
||||
|
||||
imageView = new ImageView();
|
||||
@ -796,8 +820,10 @@ public class CreateOfferView extends ActivatableViewAndModel<AnchorPane, CreateO
|
||||
|
||||
addAmountPriceFields();
|
||||
addSecondRow();
|
||||
addThirdRow();
|
||||
|
||||
Tuple2<Button, Button> tuple = add2ButtonsAfterGroup(gridPane, ++gridRow, BSResources.get("createOffer.amountPriceBox.next"), BSResources.get("shared.cancel"));
|
||||
Tuple2<Button, Button> tuple = add2ButtonsAfterGroup(gridPane, ++gridRow,
|
||||
BSResources.get("createOffer.amountPriceBox.next"), BSResources.get("shared.cancel"));
|
||||
nextButton = tuple.first;
|
||||
editOfferElements.add(nextButton);
|
||||
nextButton.disableProperty().bind(model.isNextButtonDisabled);
|
||||
@ -1036,8 +1062,9 @@ public class CreateOfferView extends ActivatableViewAndModel<AnchorPane, CreateO
|
||||
fixedPriceButton.setMouseTransparent(fixedPriceSelected);
|
||||
useMarketBasedPriceButton.setMouseTransparent(!fixedPriceSelected);
|
||||
|
||||
fixedPriceButton.setStyle(fixedPriceSelected ? "-fx-background-color: -bs-blue-transparent" : "-fx-background-color: -bs-very-light-grey");
|
||||
useMarketBasedPriceButton.setStyle(!fixedPriceSelected ? "-fx-background-color: -bs-blue-transparent" : "-fx-background-color: -bs-very-light-grey");
|
||||
final boolean finalFixedPriceSelected = fixedPriceSelected;
|
||||
fixedPriceButton.setStyle(finalFixedPriceSelected ? "-fx-background-color: -bs-blue-transparent" : "-fx-background-color: -bs-very-light-grey");
|
||||
useMarketBasedPriceButton.setStyle(!finalFixedPriceSelected ? "-fx-background-color: -bs-blue-transparent" : "-fx-background-color: -bs-very-light-grey");
|
||||
|
||||
if (fixedPriceSelected) {
|
||||
if (firstRowHBox.getChildren().contains(percentagePriceBox))
|
||||
@ -1047,7 +1074,7 @@ public class CreateOfferView extends ActivatableViewAndModel<AnchorPane, CreateO
|
||||
if (!firstRowHBox.getChildren().contains(fixedPriceBox))
|
||||
firstRowHBox.getChildren().add(2, fixedPriceBox);
|
||||
if (!secondRowHBox.getChildren().contains(percentagePriceBox))
|
||||
secondRowHBox.getChildren().add(percentagePriceBox);
|
||||
secondRowHBox.getChildren().add(2, percentagePriceBox);
|
||||
} else {
|
||||
if (firstRowHBox.getChildren().contains(fixedPriceBox))
|
||||
firstRowHBox.getChildren().remove(fixedPriceBox);
|
||||
@ -1056,7 +1083,7 @@ public class CreateOfferView extends ActivatableViewAndModel<AnchorPane, CreateO
|
||||
if (!firstRowHBox.getChildren().contains(percentagePriceBox))
|
||||
firstRowHBox.getChildren().add(2, percentagePriceBox);
|
||||
if (!secondRowHBox.getChildren().contains(fixedPriceBox))
|
||||
secondRowHBox.getChildren().add(fixedPriceBox);
|
||||
secondRowHBox.getChildren().add(2, fixedPriceBox);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1098,11 +1125,37 @@ public class CreateOfferView extends ActivatableViewAndModel<AnchorPane, CreateO
|
||||
secondRowHBox.getChildren().addAll(amountInputBoxTuple.second, xLabel, fixedPriceBox);
|
||||
GridPane.setRowIndex(secondRowHBox, ++gridRow);
|
||||
GridPane.setColumnIndex(secondRowHBox, 1);
|
||||
GridPane.setMargin(secondRowHBox, new Insets(5, 10, 5, 0));
|
||||
GridPane.setMargin(secondRowHBox, new Insets(0, 10, 0, 0));
|
||||
GridPane.setColumnSpan(secondRowHBox, 2);
|
||||
gridPane.getChildren().add(secondRowHBox);
|
||||
}
|
||||
|
||||
private void addThirdRow() {
|
||||
// security deposit
|
||||
Tuple3<HBox, InputTextField, Label> securityDepositTuple = FormBuilder.getValueCurrencyBox(
|
||||
BSResources.get("createOffer.securityDeposit.prompt"));
|
||||
HBox securityDepositValueCurrencyBox = securityDepositTuple.first;
|
||||
securityDepositTextField = securityDepositTuple.second;
|
||||
editOfferElements.add(securityDepositTextField);
|
||||
securityDepositBtcLabel = securityDepositTuple.third;
|
||||
editOfferElements.add(securityDepositBtcLabel);
|
||||
Tuple2<Label, VBox> securityDepositInputBoxTuple = getTradeInputBox(securityDepositValueCurrencyBox,
|
||||
BSResources.get("createOffer.securityDepositBox.description"));
|
||||
securityDepositDescriptionLabel = securityDepositInputBoxTuple.first;
|
||||
editOfferElements.add(securityDepositDescriptionLabel);
|
||||
VBox securityDepositBox = securityDepositInputBoxTuple.second;
|
||||
|
||||
thirdRowHBox = new HBox();
|
||||
thirdRowHBox.setSpacing(5);
|
||||
thirdRowHBox.setAlignment(Pos.CENTER_LEFT);
|
||||
thirdRowHBox.getChildren().add(securityDepositBox);
|
||||
GridPane.setRowIndex(thirdRowHBox, ++gridRow);
|
||||
GridPane.setColumnIndex(thirdRowHBox, 1);
|
||||
GridPane.setMargin(thirdRowHBox, new Insets(0, 10, 5, 0));
|
||||
GridPane.setColumnSpan(thirdRowHBox, 2);
|
||||
gridPane.getChildren().add(thirdRowHBox);
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// PayInfo
|
||||
@ -1130,7 +1183,7 @@ public class CreateOfferView extends ActivatableViewAndModel<AnchorPane, CreateO
|
||||
if (model.isSellOffer())
|
||||
addPayInfoEntry(infoGridPane, i++, BSResources.get("createOffer.fundsBox.tradeAmount"), model.tradeAmount.get());
|
||||
|
||||
addPayInfoEntry(infoGridPane, i++, BSResources.get("createOffer.fundsBox.securityDeposit"), model.getSecurityDeposit());
|
||||
addPayInfoEntry(infoGridPane, i++, BSResources.get("createOffer.fundsBox.securityDeposit"), model.getSecurityDepositInfo());
|
||||
addPayInfoEntry(infoGridPane, i++, BSResources.get("createOffer.fundsBox.offerFee"), model.getCreateOfferFee());
|
||||
addPayInfoEntry(infoGridPane, i++, BSResources.get("createOffer.fundsBox.networkFee"), model.getTxFee());
|
||||
Separator separator = new Separator();
|
||||
|
@ -18,6 +18,7 @@
|
||||
package io.bitsquare.gui.main.offer.createoffer;
|
||||
|
||||
import io.bitsquare.app.DevFlags;
|
||||
import io.bitsquare.btc.Restrictions;
|
||||
import io.bitsquare.btc.provider.price.MarketPrice;
|
||||
import io.bitsquare.btc.provider.price.PriceFeedService;
|
||||
import io.bitsquare.common.Timer;
|
||||
@ -40,6 +41,7 @@ import io.bitsquare.gui.util.GUIUtil;
|
||||
import io.bitsquare.gui.util.validation.BtcValidator;
|
||||
import io.bitsquare.gui.util.validation.FiatValidator;
|
||||
import io.bitsquare.gui.util.validation.InputValidator;
|
||||
import io.bitsquare.gui.util.validation.SecurityDepositValidator;
|
||||
import io.bitsquare.locale.BSResources;
|
||||
import io.bitsquare.locale.CurrencyUtil;
|
||||
import io.bitsquare.locale.TradeCurrency;
|
||||
@ -59,6 +61,7 @@ import static javafx.beans.binding.Bindings.createStringBinding;
|
||||
|
||||
class CreateOfferViewModel extends ActivatableWithDataModel<CreateOfferDataModel> implements ViewModel {
|
||||
private final BtcValidator btcValidator;
|
||||
private final SecurityDepositValidator securityDepositValidator;
|
||||
private final P2PService p2PService;
|
||||
private PriceFeedService priceFeedService;
|
||||
private Preferences preferences;
|
||||
@ -74,6 +77,7 @@ class CreateOfferViewModel extends ActivatableWithDataModel<CreateOfferDataModel
|
||||
|
||||
final StringProperty amount = new SimpleStringProperty();
|
||||
final StringProperty minAmount = new SimpleStringProperty();
|
||||
final StringProperty securityDeposit = 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)
|
||||
@ -106,6 +110,7 @@ class CreateOfferViewModel extends ActivatableWithDataModel<CreateOfferDataModel
|
||||
final ObjectProperty<InputValidator.ValidationResult> minAmountValidationResult = new SimpleObjectProperty<>();
|
||||
final ObjectProperty<InputValidator.ValidationResult> priceValidationResult = new SimpleObjectProperty<>();
|
||||
final ObjectProperty<InputValidator.ValidationResult> volumeValidationResult = new SimpleObjectProperty<>();
|
||||
final ObjectProperty<InputValidator.ValidationResult> securityDepositValidationResult = new SimpleObjectProperty<>();
|
||||
|
||||
// Those are needed for the addressTextField
|
||||
final ObjectProperty<Address> address = new SimpleObjectProperty<>();
|
||||
@ -114,10 +119,14 @@ class CreateOfferViewModel extends ActivatableWithDataModel<CreateOfferDataModel
|
||||
private ChangeListener<String> minAmountStringListener;
|
||||
private ChangeListener<String> priceStringListener, marketPriceMarginStringListener;
|
||||
private ChangeListener<String> volumeStringListener;
|
||||
private ChangeListener<String> securityDepositStringListener;
|
||||
|
||||
private ChangeListener<Coin> amountAsCoinListener;
|
||||
private ChangeListener<Coin> minAmountAsCoinListener;
|
||||
private ChangeListener<Price> priceListener;
|
||||
private ChangeListener<Volume> volumeListener;
|
||||
private ChangeListener<Coin> securityDepositAsCoinListener;
|
||||
|
||||
private ChangeListener<Boolean> isWalletFundedListener;
|
||||
//private ChangeListener<Coin> feeFromFundingTxListener;
|
||||
private ChangeListener<String> errorMessageListener;
|
||||
@ -125,7 +134,7 @@ class CreateOfferViewModel extends ActivatableWithDataModel<CreateOfferDataModel
|
||||
private Timer timeoutTimer;
|
||||
private boolean inputIsMarketBasedPrice;
|
||||
private ChangeListener<Boolean> useMarketBasedPriceListener;
|
||||
private boolean ignorePriceStringListener, ignoreVolumeStringListener, ignoreAmountStringListener;
|
||||
private boolean ignorePriceStringListener, ignoreVolumeStringListener, ignoreAmountStringListener, ignoreSecurityDepositStringListener;
|
||||
private MarketPrice marketPrice;
|
||||
final IntegerProperty marketPriceAvailableProperty = new SimpleIntegerProperty(-1);
|
||||
private ChangeListener<Number> currenciesUpdateListener;
|
||||
@ -137,12 +146,15 @@ class CreateOfferViewModel extends ActivatableWithDataModel<CreateOfferDataModel
|
||||
|
||||
@Inject
|
||||
public CreateOfferViewModel(CreateOfferDataModel dataModel, FiatValidator fiatValidator, BtcValidator btcValidator,
|
||||
P2PService p2PService, PriceFeedService priceFeedService, Preferences preferences, Navigation navigation,
|
||||
SecurityDepositValidator securityDepositValidator,
|
||||
P2PService p2PService, PriceFeedService priceFeedService,
|
||||
Preferences preferences, Navigation navigation,
|
||||
BSFormatter formatter) {
|
||||
super(dataModel);
|
||||
|
||||
this.fiatValidator = fiatValidator;
|
||||
this.btcValidator = btcValidator;
|
||||
this.securityDepositValidator = securityDepositValidator;
|
||||
this.p2PService = p2PService;
|
||||
this.priceFeedService = priceFeedService;
|
||||
this.preferences = preferences;
|
||||
@ -192,6 +204,10 @@ class CreateOfferViewModel extends ActivatableWithDataModel<CreateOfferDataModel
|
||||
directionLabel = BSResources.get("shared.sellBitcoin");
|
||||
amountDescription = BSResources.get("createOffer.amountPriceBox.amountDescription", BSResources.get("shared.sell"));
|
||||
}
|
||||
|
||||
securityDeposit.set(formatter.formatCoin(dataModel.securityDeposit.get()));
|
||||
|
||||
updateMarketPriceAvailable();
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -253,10 +269,8 @@ class CreateOfferViewModel extends ActivatableWithDataModel<CreateOfferDataModel
|
||||
updateButtonDisableState();
|
||||
};
|
||||
priceStringListener = (ov, oldValue, newValue) -> {
|
||||
updateMarketPriceAvailable();
|
||||
final String currencyCode = dataModel.tradeCurrencyCode.get();
|
||||
marketPrice = priceFeedService.getMarketPrice(currencyCode);
|
||||
marketPriceAvailableProperty.set(marketPrice == null ? 0 : 1);
|
||||
|
||||
if (!ignorePriceStringListener) {
|
||||
if (isPriceInputValid(newValue).isValid) {
|
||||
setPriceToModel();
|
||||
@ -352,6 +366,17 @@ class CreateOfferViewModel extends ActivatableWithDataModel<CreateOfferDataModel
|
||||
updateButtonDisableState();
|
||||
}
|
||||
};
|
||||
securityDepositStringListener = (ov, oldValue, newValue) -> {
|
||||
if (!ignoreSecurityDepositStringListener) {
|
||||
if (securityDepositValidator.validate(newValue).isValid) {
|
||||
setSecurityDepositToModel();
|
||||
dataModel.calculateTotalToPay();
|
||||
}
|
||||
updateButtonDisableState();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
amountAsCoinListener = (ov, oldValue, newValue) -> {
|
||||
if (newValue != null)
|
||||
amount.set(formatter.formatCoin(newValue));
|
||||
@ -383,19 +408,30 @@ class CreateOfferViewModel extends ActivatableWithDataModel<CreateOfferDataModel
|
||||
ignoreVolumeStringListener = false;
|
||||
};
|
||||
|
||||
securityDepositAsCoinListener = (ov, oldValue, newValue) -> {
|
||||
if (newValue != null)
|
||||
securityDeposit.set(formatter.formatCoin(newValue));
|
||||
else
|
||||
securityDeposit.set("");
|
||||
};
|
||||
|
||||
|
||||
isWalletFundedListener = (ov, oldValue, newValue) -> updateButtonDisableState();
|
||||
/* feeFromFundingTxListener = (ov, oldValue, newValue) -> {
|
||||
updateButtonDisableState();
|
||||
};*/
|
||||
|
||||
currenciesUpdateListener = (observable, oldValue, newValue) -> {
|
||||
final String currencyCode = dataModel.tradeCurrencyCode.get();
|
||||
marketPrice = priceFeedService.getMarketPrice(currencyCode);
|
||||
marketPriceAvailableProperty.set(marketPrice == null ? 0 : 1);
|
||||
updateMarketPriceAvailable();
|
||||
updateButtonDisableState();
|
||||
};
|
||||
}
|
||||
|
||||
private void updateMarketPriceAvailable() {
|
||||
marketPrice = priceFeedService.getMarketPrice(dataModel.tradeCurrencyCode.get());
|
||||
marketPriceAvailableProperty.set(marketPrice == null ? 0 : 1);
|
||||
}
|
||||
|
||||
private void addListeners() {
|
||||
// Bidirectional bindings are used for all input fields: amount, price, volume and minAmount
|
||||
// We do volume/amount calculation during input, so user has immediate feedback
|
||||
@ -405,12 +441,14 @@ class CreateOfferViewModel extends ActivatableWithDataModel<CreateOfferDataModel
|
||||
marketPriceMargin.addListener(marketPriceMarginStringListener);
|
||||
dataModel.useMarketBasedPrice.addListener(useMarketBasedPriceListener);
|
||||
volume.addListener(volumeStringListener);
|
||||
securityDeposit.addListener(securityDepositStringListener);
|
||||
|
||||
// Binding with Bindings.createObjectBinding does not work because of bi-directional binding
|
||||
dataModel.amount.addListener(amountAsCoinListener);
|
||||
dataModel.minAmount.addListener(minAmountAsCoinListener);
|
||||
dataModel.price.addListener(priceListener);
|
||||
dataModel.volume.addListener(volumeListener);
|
||||
dataModel.securityDeposit.addListener(securityDepositAsCoinListener);
|
||||
|
||||
// dataModel.feeFromFundingTxProperty.addListener(feeFromFundingTxListener);
|
||||
dataModel.isWalletFunded.addListener(isWalletFundedListener);
|
||||
@ -425,12 +463,14 @@ class CreateOfferViewModel extends ActivatableWithDataModel<CreateOfferDataModel
|
||||
marketPriceMargin.removeListener(marketPriceMarginStringListener);
|
||||
dataModel.useMarketBasedPrice.removeListener(useMarketBasedPriceListener);
|
||||
volume.removeListener(volumeStringListener);
|
||||
securityDeposit.removeListener(securityDepositStringListener);
|
||||
|
||||
// Binding with Bindings.createObjectBinding does not work because of bi-directional binding
|
||||
dataModel.amount.removeListener(amountAsCoinListener);
|
||||
dataModel.minAmount.removeListener(minAmountAsCoinListener);
|
||||
dataModel.price.removeListener(priceListener);
|
||||
dataModel.volume.removeListener(volumeListener);
|
||||
dataModel.securityDeposit.removeListener(securityDepositAsCoinListener);
|
||||
|
||||
//dataModel.feeFromFundingTxProperty.removeListener(feeFromFundingTxListener);
|
||||
dataModel.isWalletFunded.removeListener(isWalletFundedListener);
|
||||
@ -449,7 +489,7 @@ class CreateOfferViewModel extends ActivatableWithDataModel<CreateOfferDataModel
|
||||
boolean initWithData(Offer.Direction direction, TradeCurrency tradeCurrency) {
|
||||
boolean result = dataModel.initWithData(direction, tradeCurrency);
|
||||
if (dataModel.paymentAccount != null)
|
||||
btcValidator.setMaxTradeLimitInBitcoin(dataModel.paymentAccount.getPaymentMethod().getMaxTradeLimit());
|
||||
btcValidator.setMaxValueInBitcoin(dataModel.paymentAccount.getPaymentMethod().getMaxTradeLimit());
|
||||
|
||||
return result;
|
||||
}
|
||||
@ -508,7 +548,7 @@ class CreateOfferViewModel extends ActivatableWithDataModel<CreateOfferDataModel
|
||||
}
|
||||
|
||||
public void onPaymentAccountSelected(PaymentAccount paymentAccount) {
|
||||
btcValidator.setMaxTradeLimitInBitcoin(paymentAccount.getPaymentMethod().getMaxTradeLimit());
|
||||
btcValidator.setMaxValueInBitcoin(paymentAccount.getPaymentMethod().getMaxTradeLimit());
|
||||
dataModel.onPaymentAccountSelected(paymentAccount);
|
||||
if (amount.get() != null)
|
||||
amountValidationResult.set(isBtcInputValid(amount.get()));
|
||||
@ -645,6 +685,46 @@ class CreateOfferViewModel extends ActivatableWithDataModel<CreateOfferDataModel
|
||||
}
|
||||
}
|
||||
|
||||
void onFocusOutSecurityDepositTextField(boolean oldValue, boolean newValue, String userInput) {
|
||||
if (oldValue && !newValue) {
|
||||
InputValidator.ValidationResult result = securityDepositValidator.validate(securityDeposit.get());
|
||||
securityDepositValidationResult.set(result);
|
||||
if (result.isValid) {
|
||||
Coin defaultSecurityDeposit = Restrictions.DEFAULT_SECURITY_DEPOSIT;
|
||||
String securityDepositLowerAsDefault = "securityDepositLowerAsDefault";
|
||||
if (preferences.showAgain(securityDepositLowerAsDefault) &&
|
||||
formatter.parseToCoin(securityDeposit.get()).compareTo(defaultSecurityDeposit) < 0) {
|
||||
new Popup<>()
|
||||
.warning("You have set the security deposit to a lower value than the recommended default value of " +
|
||||
formatter.formatCoinWithCode(defaultSecurityDeposit) + ".\n" +
|
||||
"Are you sure you want to use a lower security deposit?\n" +
|
||||
"It gives you less protection in case the trading peer does not follow the trade protocol.")
|
||||
.width(800)
|
||||
.actionButtonText("No, reset to the default value")
|
||||
.onAction(() -> {
|
||||
dataModel.setSecurityDeposit(defaultSecurityDeposit);
|
||||
ignoreSecurityDepositStringListener = true;
|
||||
securityDeposit.set(formatter.formatCoin(dataModel.securityDeposit.get()));
|
||||
ignoreSecurityDepositStringListener = false;
|
||||
})
|
||||
.closeButtonText("Yes, use my lower value")
|
||||
.onClose(() -> applySecurityDepositOnFocusOut(result))
|
||||
.dontShowAgainId(securityDepositLowerAsDefault, preferences)
|
||||
.show();
|
||||
} else {
|
||||
applySecurityDepositOnFocusOut(result);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void applySecurityDepositOnFocusOut(InputValidator.ValidationResult result) {
|
||||
setSecurityDepositToModel();
|
||||
ignoreSecurityDepositStringListener = true;
|
||||
securityDeposit.set(formatter.formatCoin(dataModel.securityDeposit.get()));
|
||||
ignoreSecurityDepositStringListener = false;
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Getters
|
||||
@ -692,9 +772,9 @@ class CreateOfferViewModel extends ActivatableWithDataModel<CreateOfferDataModel
|
||||
return formatter.formatCoinWithCode(dataModel.amount.get());
|
||||
}
|
||||
|
||||
public String getSecurityDeposit() {
|
||||
return formatter.formatCoinWithCode(dataModel.getSecurityDepositAsCoin()) +
|
||||
GUIUtil.getPercentageOfTradeAmount(dataModel.getSecurityDepositAsCoin(), dataModel.amount.get(), formatter);
|
||||
public String getSecurityDepositInfo() {
|
||||
return formatter.formatCoinWithCode(dataModel.getSecurityDeposit()) +
|
||||
GUIUtil.getPercentageOfTradeAmount(dataModel.getSecurityDeposit(), dataModel.amount.get(), formatter);
|
||||
}
|
||||
|
||||
public String getCreateOfferFee() {
|
||||
@ -795,6 +875,15 @@ class CreateOfferViewModel extends ActivatableWithDataModel<CreateOfferDataModel
|
||||
}
|
||||
}
|
||||
|
||||
private void setSecurityDepositToModel() {
|
||||
if (securityDeposit.get() != null && !securityDeposit.get().isEmpty()) {
|
||||
dataModel.setSecurityDeposit(formatter.parseToCoinWith4Decimals(securityDeposit.get()));
|
||||
} else {
|
||||
dataModel.setSecurityDeposit(null);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private InputValidator.ValidationResult isBtcInputValid(String input) {
|
||||
return btcValidator.validate(input);
|
||||
}
|
||||
@ -841,6 +930,7 @@ class CreateOfferViewModel extends ActivatableWithDataModel<CreateOfferDataModel
|
||||
boolean inputDataValid = isBtcInputValid(amount.get()).isValid &&
|
||||
isBtcInputValid(minAmount.get()).isValid &&
|
||||
isPriceInputValid(price.get()).isValid &&
|
||||
securityDepositValidator.validate(securityDeposit.get()).isValid &&
|
||||
dataModel.price.get() != null &&
|
||||
dataModel.price.get().getValue() != 0 &&
|
||||
isVolumeInputValid(volume.get()).isValid &&
|
||||
|
@ -177,7 +177,7 @@ class TakeOfferViewModel extends ActivatableWithDataModel<TakeOfferDataModel> im
|
||||
offer.errorMessageProperty().addListener(offerErrorListener);
|
||||
errorMessage.set(offer.errorMessageProperty().get());
|
||||
|
||||
btcValidator.setMaxTradeLimitInBitcoin(offer.getAmount());
|
||||
btcValidator.setMaxValueInBitcoin(offer.getAmount());
|
||||
}
|
||||
|
||||
|
||||
@ -205,7 +205,7 @@ class TakeOfferViewModel extends ActivatableWithDataModel<TakeOfferDataModel> im
|
||||
public void onPaymentAccountSelected(PaymentAccount paymentAccount) {
|
||||
dataModel.onPaymentAccountSelected(paymentAccount);
|
||||
if (offer != null)
|
||||
btcValidator.setMaxTradeLimitInBitcoin(offer.getAmount());
|
||||
btcValidator.setMaxValueInBitcoin(offer.getAmount());
|
||||
}
|
||||
|
||||
public void onShowPayFundsScreen() {
|
||||
|
@ -943,7 +943,7 @@ public class FormBuilder {
|
||||
descriptionLabel.setPrefWidth(170);
|
||||
|
||||
VBox box = new VBox();
|
||||
box.setSpacing(4);
|
||||
box.setSpacing(2);
|
||||
box.getChildren().addAll(descriptionLabel, amountValueBox);
|
||||
return new Tuple2<>(descriptionLabel, box);
|
||||
}
|
||||
|
@ -78,7 +78,7 @@ public class GUIUtil {
|
||||
"sufficiently high so that the funding transaction will be accepted by the miners.\n" +
|
||||
"Otherwise the trade transactions cannot be confirmed and a trade would end up in a dispute.\n\n" +
|
||||
"The recommended fee is about 120 Satoshi/Byte which is for an average transaction about 0.0005 BTC.\n\n" +
|
||||
"You can view typically used fees at: https://tradeblock.com/blockchain")
|
||||
"You can check out the currently recommended fees at: https://bitcoinfees.21.co")
|
||||
.dontShowAgainId(key, Preferences.INSTANCE)
|
||||
.onClose(runnable::run)
|
||||
.closeButtonText("I understand")
|
||||
|
@ -27,19 +27,19 @@ import java.math.BigDecimal;
|
||||
|
||||
public class BtcValidator extends NumberValidator {
|
||||
|
||||
private final BSFormatter formatter;
|
||||
protected final BSFormatter formatter;
|
||||
|
||||
|
||||
@Nullable
|
||||
private Coin maxTradeLimitInBitcoin;
|
||||
protected Coin maxValueInBitcoin;
|
||||
|
||||
@Inject
|
||||
public BtcValidator(BSFormatter formatter) {
|
||||
this.formatter = formatter;
|
||||
}
|
||||
|
||||
public void setMaxTradeLimitInBitcoin(Coin maxTradeLimitInBitcoin) {
|
||||
this.maxTradeLimitInBitcoin = maxTradeLimitInBitcoin;
|
||||
public void setMaxValueInBitcoin(Coin maxValueInBitcoin) {
|
||||
this.maxValueInBitcoin = maxValueInBitcoin;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -72,8 +72,8 @@ public class BtcValidator extends NumberValidator {
|
||||
protected ValidationResult validateIfNotExceedsMaxBtcValue(String input) {
|
||||
try {
|
||||
final Coin coin = Coin.parseCoin(input);
|
||||
if (maxTradeLimitInBitcoin != null && coin.compareTo(maxTradeLimitInBitcoin) > 0)
|
||||
return new ValidationResult(false, BSResources.get("validation.btc.toLarge", formatter.formatCoinWithCode(maxTradeLimitInBitcoin)));
|
||||
if (maxValueInBitcoin != null && coin.compareTo(maxValueInBitcoin) > 0)
|
||||
return new ValidationResult(false, BSResources.get("validation.btc.toLarge", formatter.formatCoinWithCode(maxValueInBitcoin)));
|
||||
else
|
||||
return new ValidationResult(true);
|
||||
} catch (Throwable t) {
|
||||
|
@ -0,0 +1,69 @@
|
||||
/*
|
||||
* This file is part of Bitsquare.
|
||||
*
|
||||
* Bitsquare is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or (at
|
||||
* your option) any later version.
|
||||
*
|
||||
* Bitsquare is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
|
||||
* License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Bitsquare. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package io.bitsquare.gui.util.validation;
|
||||
|
||||
import io.bitsquare.btc.Restrictions;
|
||||
import io.bitsquare.gui.util.BSFormatter;
|
||||
import io.bitsquare.locale.BSResources;
|
||||
import org.bitcoinj.core.Coin;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
public class SecurityDepositValidator extends BtcValidator {
|
||||
|
||||
@Inject
|
||||
public SecurityDepositValidator(BSFormatter formatter) {
|
||||
super(formatter);
|
||||
setMaxValueInBitcoin(Restrictions.MAX_SECURITY_DEPOSIT);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public ValidationResult validate(String input) {
|
||||
ValidationResult result = validateIfNotEmpty(input);
|
||||
if (result.isValid) {
|
||||
input = cleanInput(input);
|
||||
result = validateIfNumber(input);
|
||||
}
|
||||
|
||||
if (result.isValid) {
|
||||
result = validateIfNotZero(input)
|
||||
.and(validateIfNotNegative(input))
|
||||
.and(validateIfNotTooLowBtcValue(input))
|
||||
.and(validateIfNotFractionalBtcValue(input))
|
||||
.and(validateIfNotExceedsMaxBtcValue(input));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
protected ValidationResult validateIfNotTooLowBtcValue(String input) {
|
||||
try {
|
||||
final Coin coin = Coin.parseCoin(input);
|
||||
Coin minSecurityDeposit = Restrictions.MIN_SECURITY_DEPOSIT;
|
||||
if (coin.compareTo(minSecurityDeposit) < 0)
|
||||
return new ValidationResult(false,
|
||||
BSResources.get("validation.securityDeposit.toSmall", formatter.formatCoinWithCode(minSecurityDeposit)));
|
||||
else
|
||||
return new ValidationResult(true);
|
||||
} catch (Throwable t) {
|
||||
return new ValidationResult(false, "Invalid input: " + t.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
@ -82,7 +82,8 @@ validation.negative=A negative value is not allowed.
|
||||
validation.fiat.toSmall=Input smaller than minimum possible amount is not allowed.
|
||||
validation.fiat.toLarge=Input larger than maximum possible amount is not allowed.
|
||||
validation.btc.toSmall=Input results in a bitcoin value with a fraction of the smallest unit (Satoshi).
|
||||
validation.btc.toLarge=Input larger than maximum trading amount of {0} is not allowed.
|
||||
validation.btc.toLarge=Input larger than {0} is not allowed.
|
||||
validation.securityDeposit.toSmall=Input smaller than {0} is not allowed.
|
||||
validation.passwordTooShort=The password you entered is too short. It needs to have min. 8 characters.
|
||||
validation.passwordTooLong=The password you entered is too long. It cannot be longer than 50 characters.
|
||||
validation.sortCodeNumber={0} must consist of {1} numbers.
|
||||
@ -113,6 +114,9 @@ createOffer.amountPriceBox.error.message=An error occurred when placing the offe
|
||||
createOffer.validation.amountSmallerThanMinAmount=Amount cannot be smaller than minimum amount.
|
||||
createOffer.validation.minAmountLargerThanAmount=Minimum amount cannot be larger than amount.
|
||||
|
||||
createOffer.securityDeposit.prompt=Security deposit in BTC
|
||||
createOffer.securityDepositBox.description=Customize security deposit
|
||||
|
||||
createOffer.fundsBox.title=Fund your offer
|
||||
createOffer.fundsBox.totalsNeeded=Funds needed:
|
||||
createOffer.fundsBox.totalsNeeded.prompt=Will be calculated from the bitcoin amount entered above
|
||||
|
Loading…
Reference in New Issue
Block a user