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:
Manfred Karrer 2017-02-20 17:58:07 -05:00
parent 159b208273
commit 827c581a73
14 changed files with 314 additions and 116 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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() {

View File

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

View File

@ -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")

View File

@ -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) {

View File

@ -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());
}
}
}

View File

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