set minimum trade amount to 0.05 XMR (#1857)

This commit is contained in:
woodser 2025-07-21 09:57:54 -04:00 committed by GitHub
parent 3680e1d4ee
commit fd2c0f335f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
12 changed files with 75 additions and 67 deletions

View file

@ -43,7 +43,7 @@ import java.util.stream.Collectors;
import static haveno.apitest.config.ApiTestConfig.BTC; import static haveno.apitest.config.ApiTestConfig.BTC;
import static haveno.apitest.config.ApiTestRateMeterInterceptorConfig.getTestRateMeterInterceptorConfig; import static haveno.apitest.config.ApiTestRateMeterInterceptorConfig.getTestRateMeterInterceptorConfig;
import static haveno.cli.table.builder.TableType.BTC_BALANCE_TBL; import static haveno.cli.table.builder.TableType.BTC_BALANCE_TBL;
import static haveno.core.xmr.wallet.Restrictions.getDefaultSecurityDepositAsPercent; import static haveno.core.xmr.wallet.Restrictions.getDefaultSecurityDepositPct;
import static java.lang.String.format; import static java.lang.String.format;
import static java.nio.charset.StandardCharsets.UTF_8; import static java.nio.charset.StandardCharsets.UTF_8;
import static java.util.Arrays.stream; import static java.util.Arrays.stream;
@ -158,7 +158,7 @@ public class MethodTest extends ApiTestCase {
} }
public static final Supplier<Double> defaultSecurityDepositPct = () -> { public static final Supplier<Double> defaultSecurityDepositPct = () -> {
var defaultPct = BigDecimal.valueOf(getDefaultSecurityDepositAsPercent()); var defaultPct = BigDecimal.valueOf(getDefaultSecurityDepositPct());
if (defaultPct.precision() != 2) if (defaultPct.precision() != 2)
throw new IllegalStateException(format( throw new IllegalStateException(format(
"Unexpected decimal precision, expected 2 but actual is %d%n." "Unexpected decimal precision, expected 2 but actual is %d%n."

View file

@ -404,7 +404,7 @@ public class OfferBookService {
} }
// validate max offers with same key images // validate max offers with same key images
if (numOffersWithSharedKeyImages > Restrictions.MAX_OFFERS_WITH_SHARED_FUNDS) throw new RuntimeException("More than " + Restrictions.MAX_OFFERS_WITH_SHARED_FUNDS + " offers exist with same same key images as new offerId=" + offerPayload.getId()); if (numOffersWithSharedKeyImages > Restrictions.getMaxOffersWithSharedFunds()) throw new RuntimeException("More than " + Restrictions.getMaxOffersWithSharedFunds() + " offers exist with same same key images as new offerId=" + offerPayload.getId());
} }
} }

View file

@ -61,8 +61,8 @@ import haveno.core.trade.statistics.ReferralIdService;
import haveno.core.user.AutoConfirmSettings; import haveno.core.user.AutoConfirmSettings;
import haveno.core.user.Preferences; import haveno.core.user.Preferences;
import haveno.core.util.coin.CoinFormatter; import haveno.core.util.coin.CoinFormatter;
import static haveno.core.xmr.wallet.Restrictions.getMaxSecurityDepositAsPercent; import static haveno.core.xmr.wallet.Restrictions.getMaxSecurityDepositPct;
import static haveno.core.xmr.wallet.Restrictions.getMinSecurityDepositAsPercent; import static haveno.core.xmr.wallet.Restrictions.getMinSecurityDepositPct;
import haveno.network.p2p.P2PService; import haveno.network.p2p.P2PService;
import java.math.BigInteger; import java.math.BigInteger;
import java.util.HashMap; import java.util.HashMap;
@ -240,12 +240,12 @@ public class OfferUtil {
PaymentAccount paymentAccount, PaymentAccount paymentAccount,
String currencyCode) { String currencyCode) {
checkNotNull(p2PService.getAddress(), "Address must not be null"); checkNotNull(p2PService.getAddress(), "Address must not be null");
checkArgument(securityDeposit <= getMaxSecurityDepositAsPercent(), checkArgument(securityDeposit <= getMaxSecurityDepositPct(),
"securityDeposit must not exceed " + "securityDeposit must not exceed " +
getMaxSecurityDepositAsPercent()); getMaxSecurityDepositPct());
checkArgument(securityDeposit >= getMinSecurityDepositAsPercent(), checkArgument(securityDeposit >= getMinSecurityDepositPct(),
"securityDeposit must not be less than " + "securityDeposit must not be less than " +
getMinSecurityDepositAsPercent() + " but was " + securityDeposit); getMinSecurityDepositPct() + " but was " + securityDeposit);
checkArgument(!filterManager.isCurrencyBanned(currencyCode), checkArgument(!filterManager.isCurrencyBanned(currencyCode),
Res.get("offerbook.warning.currencyBanned")); Res.get("offerbook.warning.currencyBanned"));
checkArgument(!filterManager.isPaymentMethodBanned(paymentAccount.getPaymentMethod()), checkArgument(!filterManager.isPaymentMethodBanned(paymentAccount.getPaymentMethod()),

View file

@ -540,8 +540,8 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
// check clone limit // check clone limit
int numClones = getOpenOfferGroup(sourceOffer.getGroupId()).size(); int numClones = getOpenOfferGroup(sourceOffer.getGroupId()).size();
if (numClones >= Restrictions.MAX_OFFERS_WITH_SHARED_FUNDS) { if (numClones >= Restrictions.getMaxOffersWithSharedFunds()) {
errorMessageHandler.handleErrorMessage("Cannot create offer because maximum number of " + Restrictions.MAX_OFFERS_WITH_SHARED_FUNDS + " cloned offers with shared funds reached."); errorMessageHandler.handleErrorMessage("Cannot create offer because maximum number of " + Restrictions.getMaxOffersWithSharedFunds() + " cloned offers with shared funds reached.");
return; return;
} }
} }
@ -1565,8 +1565,8 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
} }
// verify max length of extra info // verify max length of extra info
if (offer.getOfferPayload().getExtraInfo() != null && offer.getOfferPayload().getExtraInfo().length() > Restrictions.MAX_EXTRA_INFO_LENGTH) { if (offer.getOfferPayload().getExtraInfo() != null && offer.getOfferPayload().getExtraInfo().length() > Restrictions.getMaxExtraInfoLength()) {
errorMessage = "Extra info is too long for offer " + request.offerId + ". Max length is " + Restrictions.MAX_EXTRA_INFO_LENGTH + " but got " + offer.getOfferPayload().getExtraInfo().length(); errorMessage = "Extra info is too long for offer " + request.offerId + ". Max length is " + Restrictions.getMaxExtraInfoLength() + " but got " + offer.getOfferPayload().getExtraInfo().length();
log.warn(errorMessage); log.warn(errorMessage);
sendAckMessage(request.getClass(), peer, request.getPubKeyRing(), request.getOfferId(), request.getUid(), false, errorMessage); sendAckMessage(request.getClass(), peer, request.getPubKeyRing(), request.getOfferId(), request.getUid(), false, errorMessage);
return; return;
@ -1611,8 +1611,8 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
} }
// verify maker security deposit // verify maker security deposit
if (offer.getSellerSecurityDepositPct() != Restrictions.MIN_SECURITY_DEPOSIT_PCT) { if (offer.getSellerSecurityDepositPct() != Restrictions.getMinSecurityDepositPct()) {
errorMessage = "Wrong seller security deposit for offer " + request.offerId + ". Expected " + Restrictions.MIN_SECURITY_DEPOSIT_PCT + " but got " + offer.getSellerSecurityDepositPct(); errorMessage = "Wrong seller security deposit for offer " + request.offerId + ". Expected " + Restrictions.getMinSecurityDepositPct() + " but got " + offer.getSellerSecurityDepositPct();
log.warn(errorMessage); log.warn(errorMessage);
sendAckMessage(request.getClass(), peer, request.getPubKeyRing(), request.getOfferId(), request.getUid(), false, errorMessage); sendAckMessage(request.getClass(), peer, request.getPubKeyRing(), request.getOfferId(), request.getUid(), false, errorMessage);
return; return;
@ -1652,16 +1652,16 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
} }
// verify seller's security deposit // verify seller's security deposit
if (offer.getSellerSecurityDepositPct() < Restrictions.MIN_SECURITY_DEPOSIT_PCT) { if (offer.getSellerSecurityDepositPct() < Restrictions.getMinSecurityDepositPct()) {
errorMessage = "Insufficient seller security deposit for offer " + request.offerId + ". Expected at least " + Restrictions.MIN_SECURITY_DEPOSIT_PCT + " but got " + offer.getSellerSecurityDepositPct(); errorMessage = "Insufficient seller security deposit for offer " + request.offerId + ". Expected at least " + Restrictions.getMinSecurityDepositPct() + " but got " + offer.getSellerSecurityDepositPct();
log.warn(errorMessage); log.warn(errorMessage);
sendAckMessage(request.getClass(), peer, request.getPubKeyRing(), request.getOfferId(), request.getUid(), false, errorMessage); sendAckMessage(request.getClass(), peer, request.getPubKeyRing(), request.getOfferId(), request.getUid(), false, errorMessage);
return; return;
} }
// verify buyer's security deposit // verify buyer's security deposit
if (offer.getBuyerSecurityDepositPct() < Restrictions.MIN_SECURITY_DEPOSIT_PCT) { if (offer.getBuyerSecurityDepositPct() < Restrictions.getMinSecurityDepositPct()) {
errorMessage = "Insufficient buyer security deposit for offer " + request.offerId + ". Expected at least " + Restrictions.MIN_SECURITY_DEPOSIT_PCT + " but got " + offer.getBuyerSecurityDepositPct(); errorMessage = "Insufficient buyer security deposit for offer " + request.offerId + ". Expected at least " + Restrictions.getMinSecurityDepositPct() + " but got " + offer.getBuyerSecurityDepositPct();
log.warn(errorMessage); log.warn(errorMessage);
sendAckMessage(request.getClass(), peer, request.getPubKeyRing(), request.getOfferId(), request.getUid(), false, errorMessage); sendAckMessage(request.getClass(), peer, request.getPubKeyRing(), request.getOfferId(), request.getUid(), false, errorMessage);
return; return;

View file

@ -59,7 +59,7 @@ public class SecurityDepositValidator extends NumberValidator {
private ValidationResult validateIfNotTooLowPercentageValue(String input) { private ValidationResult validateIfNotTooLowPercentageValue(String input) {
try { try {
double percentage = ParsingUtils.parsePercentStringToDouble(input); double percentage = ParsingUtils.parsePercentStringToDouble(input);
double minPercentage = Restrictions.getMinSecurityDepositAsPercent(); double minPercentage = Restrictions.getMinSecurityDepositPct();
if (percentage < minPercentage) if (percentage < minPercentage)
return new ValidationResult(false, return new ValidationResult(false,
Res.get("validation.inputTooSmall", FormattingUtils.formatToPercentWithSymbol(minPercentage))); Res.get("validation.inputTooSmall", FormattingUtils.formatToPercentWithSymbol(minPercentage)));
@ -73,7 +73,7 @@ public class SecurityDepositValidator extends NumberValidator {
private ValidationResult validateIfNotTooHighPercentageValue(String input) { private ValidationResult validateIfNotTooHighPercentageValue(String input) {
try { try {
double percentage = ParsingUtils.parsePercentStringToDouble(input); double percentage = ParsingUtils.parsePercentStringToDouble(input);
double maxPercentage = Restrictions.getMaxSecurityDepositAsPercent(); double maxPercentage = Restrictions.getMaxSecurityDepositPct();
if (percentage > maxPercentage) if (percentage > maxPercentage)
return new ValidationResult(false, return new ValidationResult(false,
Res.get("validation.inputTooLarge", FormattingUtils.formatToPercentWithSymbol(maxPercentage))); Res.get("validation.inputTooLarge", FormattingUtils.formatToPercentWithSymbol(maxPercentage)));

View file

@ -623,8 +623,8 @@ public final class Preferences implements PersistedDataHost, BridgeAddressProvid
} }
public void setSecurityDepositAsPercent(double securityDepositAsPercent, PaymentAccount paymentAccount) { public void setSecurityDepositAsPercent(double securityDepositAsPercent, PaymentAccount paymentAccount) {
double max = Restrictions.getMaxSecurityDepositAsPercent(); double max = Restrictions.getMaxSecurityDepositPct();
double min = Restrictions.getMinSecurityDepositAsPercent(); double min = Restrictions.getMinSecurityDepositPct();
if (PaymentAccountUtil.isCryptoCurrencyAccount(paymentAccount)) if (PaymentAccountUtil.isCryptoCurrencyAccount(paymentAccount))
prefPayload.setSecurityDepositAsPercentForCrypto(Math.min(max, Math.max(min, securityDepositAsPercent))); prefPayload.setSecurityDepositAsPercentForCrypto(Math.min(max, Math.max(min, securityDepositAsPercent)));
@ -853,12 +853,12 @@ public final class Preferences implements PersistedDataHost, BridgeAddressProvid
double value = PaymentAccountUtil.isCryptoCurrencyAccount(paymentAccount) ? double value = PaymentAccountUtil.isCryptoCurrencyAccount(paymentAccount) ?
prefPayload.getSecurityDepositAsPercentForCrypto() : prefPayload.getSecurityDepositAsPercent(); prefPayload.getSecurityDepositAsPercentForCrypto() : prefPayload.getSecurityDepositAsPercent();
if (value < Restrictions.getMinSecurityDepositAsPercent()) { if (value < Restrictions.getMinSecurityDepositPct()) {
value = Restrictions.getMinSecurityDepositAsPercent(); value = Restrictions.getMinSecurityDepositPct();
setSecurityDepositAsPercent(value, paymentAccount); setSecurityDepositAsPercent(value, paymentAccount);
} }
return value == 0 ? Restrictions.getDefaultSecurityDepositAsPercent() : value; return value == 0 ? Restrictions.getDefaultSecurityDepositPct() : value;
} }
@Override @Override

View file

@ -41,7 +41,7 @@ import java.util.Map;
import java.util.Optional; import java.util.Optional;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import static haveno.core.xmr.wallet.Restrictions.getDefaultSecurityDepositAsPercent; import static haveno.core.xmr.wallet.Restrictions.getDefaultSecurityDepositPct;
@Slf4j @Slf4j
@Data @Data
@ -120,10 +120,10 @@ public final class PreferencesPayload implements PersistableEnvelope {
private String rpcPw; private String rpcPw;
@Nullable @Nullable
private String takeOfferSelectedPaymentAccountId; private String takeOfferSelectedPaymentAccountId;
private double securityDepositAsPercent = getDefaultSecurityDepositAsPercent(); private double securityDepositAsPercent = getDefaultSecurityDepositPct();
private int ignoreDustThreshold = 600; private int ignoreDustThreshold = 600;
private int clearDataAfterDays = Preferences.CLEAR_DATA_AFTER_DAYS_DEFAULT; private int clearDataAfterDays = Preferences.CLEAR_DATA_AFTER_DAYS_DEFAULT;
private double securityDepositAsPercentForCrypto = getDefaultSecurityDepositAsPercent(); private double securityDepositAsPercentForCrypto = getDefaultSecurityDepositPct();
private int blockNotifyPort; private int blockNotifyPort;
private boolean tacAcceptedV120; private boolean tacAcceptedV120;
private double bsqAverageTrimThreshold = 0.05; private double bsqAverageTrimThreshold = 0.05;

View file

@ -26,12 +26,12 @@ import java.math.BigInteger;
public class Restrictions { public class Restrictions {
// configure restrictions // configure restrictions
public static final double MIN_SECURITY_DEPOSIT_PCT = 0.15; private static final double MIN_SECURITY_DEPOSIT_PCT = 0.15;
public static final double MAX_SECURITY_DEPOSIT_PCT = 0.5; private static final double MAX_SECURITY_DEPOSIT_PCT = 0.5;
public static BigInteger MIN_TRADE_AMOUNT = HavenoUtils.xmrToAtomicUnits(0.1); private static final BigInteger MIN_TRADE_AMOUNT = HavenoUtils.xmrToAtomicUnits(0.05);
public static BigInteger MIN_SECURITY_DEPOSIT = HavenoUtils.xmrToAtomicUnits(0.1); private static final BigInteger MIN_SECURITY_DEPOSIT = HavenoUtils.xmrToAtomicUnits(0.1);
public static int MAX_EXTRA_INFO_LENGTH = 1500; private static final int MAX_EXTRA_INFO_LENGTH = 1500;
public static int MAX_OFFERS_WITH_SHARED_FUNDS = 10; private static final int MAX_OFFERS_WITH_SHARED_FUNDS = 10;
// At mediation we require a min. payout to the losing party to keep incentive for the trader to accept the // At mediation we require a min. payout to the losing party to keep incentive for the trader to accept the
// mediated payout. For Refund agent cases we do not have that restriction. // mediated payout. For Refund agent cases we do not have that restriction.
@ -57,15 +57,15 @@ public class Restrictions {
return MIN_TRADE_AMOUNT; return MIN_TRADE_AMOUNT;
} }
public static double getDefaultSecurityDepositAsPercent() { public static double getDefaultSecurityDepositPct() {
return MIN_SECURITY_DEPOSIT_PCT; return MIN_SECURITY_DEPOSIT_PCT;
} }
public static double getMinSecurityDepositAsPercent() { public static double getMinSecurityDepositPct() {
return MIN_SECURITY_DEPOSIT_PCT; return MIN_SECURITY_DEPOSIT_PCT;
} }
public static double getMaxSecurityDepositAsPercent() { public static double getMaxSecurityDepositPct() {
return MAX_SECURITY_DEPOSIT_PCT; return MAX_SECURITY_DEPOSIT_PCT;
} }
@ -73,6 +73,14 @@ public class Restrictions {
return MIN_SECURITY_DEPOSIT; return MIN_SECURITY_DEPOSIT;
} }
public static int getMaxExtraInfoLength() {
return MAX_EXTRA_INFO_LENGTH;
}
public static int getMaxOffersWithSharedFunds() {
return MAX_OFFERS_WITH_SHARED_FUNDS;
}
// This value must be lower than MIN_BUYER_SECURITY_DEPOSIT and SELLER_SECURITY_DEPOSIT // This value must be lower than MIN_BUYER_SECURITY_DEPOSIT and SELLER_SECURITY_DEPOSIT
public static BigInteger getMinRefundAtMediatedDispute() { public static BigInteger getMinRefundAtMediatedDispute() {
if (MIN_REFUND_AT_MEDIATED_DISPUTE == null) if (MIN_REFUND_AT_MEDIATED_DISPUTE == null)

View file

@ -76,11 +76,11 @@ public class CoinUtilTest {
BigInteger result = CoinUtil.getAdjustedAmount( BigInteger result = CoinUtil.getAdjustedAmount(
HavenoUtils.xmrToAtomicUnits(0.1), HavenoUtils.xmrToAtomicUnits(0.1),
Price.valueOf("USD", 1000_0000), Price.valueOf("USD", 1000_0000),
HavenoUtils.xmrToAtomicUnits(0.1), Restrictions.getMinTradeAmount(),
HavenoUtils.xmrToAtomicUnits(0.2), HavenoUtils.xmrToAtomicUnits(0.2),
1); 1);
assertEquals( assertEquals(
HavenoUtils.formatXmr(Restrictions.MIN_TRADE_AMOUNT, true), HavenoUtils.formatXmr(Restrictions.getMinTradeAmount(), true),
HavenoUtils.formatXmr(result, true), HavenoUtils.formatXmr(result, true),
"Minimum trade amount allowed should be adjusted to the smallest trade allowed." "Minimum trade amount allowed should be adjusted to the smallest trade allowed."
); );
@ -95,7 +95,7 @@ public class CoinUtilTest {
fail("Expected IllegalArgumentException to be thrown when amount is too low."); fail("Expected IllegalArgumentException to be thrown when amount is too low.");
} catch (IllegalArgumentException iae) { } catch (IllegalArgumentException iae) {
assertEquals( assertEquals(
"amount must be above minimum of 0.1 xmr but was 0.0 xmr", "amount must be above minimum of 0.05 xmr but was 0.0 xmr",
iae.getMessage(), iae.getMessage(),
"Unexpected exception message." "Unexpected exception message."
); );
@ -104,11 +104,11 @@ public class CoinUtilTest {
result = CoinUtil.getAdjustedAmount( result = CoinUtil.getAdjustedAmount(
HavenoUtils.xmrToAtomicUnits(0.1), HavenoUtils.xmrToAtomicUnits(0.1),
Price.valueOf("USD", 1000_0000), Price.valueOf("USD", 1000_0000),
HavenoUtils.xmrToAtomicUnits(0.1), Restrictions.getMinTradeAmount(),
HavenoUtils.xmrToAtomicUnits(0.2), HavenoUtils.xmrToAtomicUnits(0.2),
1); 1);
assertEquals( assertEquals(
"0.10 XMR", "0.05 XMR",
HavenoUtils.formatXmr(result, true), HavenoUtils.formatXmr(result, true),
"Minimum allowed trade amount should not be adjusted." "Minimum allowed trade amount should not be adjusted."
); );
@ -116,11 +116,11 @@ public class CoinUtilTest {
result = CoinUtil.getAdjustedAmount( result = CoinUtil.getAdjustedAmount(
HavenoUtils.xmrToAtomicUnits(0.1), HavenoUtils.xmrToAtomicUnits(0.1),
Price.valueOf("USD", 1000_0000), Price.valueOf("USD", 1000_0000),
HavenoUtils.xmrToAtomicUnits(0.1), Restrictions.getMinTradeAmount(),
HavenoUtils.xmrToAtomicUnits(0.25), HavenoUtils.xmrToAtomicUnits(0.25),
1); 1);
assertEquals( assertEquals(
"0.10 XMR", "0.05 XMR",
HavenoUtils.formatXmr(result, true), HavenoUtils.formatXmr(result, true),
"Minimum trade amount allowed should respect maxTradeLimit and factor, if possible." "Minimum trade amount allowed should respect maxTradeLimit and factor, if possible."
); );

View file

@ -168,7 +168,7 @@ public abstract class MutableOfferDataModel extends OfferDataModel {
reserveExactAmount = preferences.getSplitOfferOutput(); reserveExactAmount = preferences.getSplitOfferOutput();
useMarketBasedPrice.set(preferences.isUsePercentageBasedPrice()); useMarketBasedPrice.set(preferences.isUsePercentageBasedPrice());
securityDepositPct.set(Restrictions.getMinSecurityDepositAsPercent()); securityDepositPct.set(Restrictions.getMinSecurityDepositPct());
paymentAccountsChangeListener = change -> fillPaymentAccounts(); paymentAccountsChangeListener = change -> fillPaymentAccounts();
} }
@ -338,7 +338,7 @@ public abstract class MutableOfferDataModel extends OfferDataModel {
} }
private void setSuggestedSecurityDeposit(PaymentAccount paymentAccount) { private void setSuggestedSecurityDeposit(PaymentAccount paymentAccount) {
var minSecurityDeposit = Restrictions.getMinSecurityDepositAsPercent(); var minSecurityDeposit = Restrictions.getMinSecurityDepositPct();
try { try {
if (getTradeCurrency() == null) { if (getTradeCurrency() == null) {
setSecurityDepositPct(minSecurityDeposit); setSecurityDepositPct(minSecurityDeposit);
@ -369,7 +369,7 @@ public abstract class MutableOfferDataModel extends OfferDataModel {
} }
// Suggested deposit is double the trade range over the previous lock time period, bounded by min/max deposit // Suggested deposit is double the trade range over the previous lock time period, bounded by min/max deposit
var suggestedSecurityDeposit = var suggestedSecurityDeposit =
Math.min(2 * (max - min) / max, Restrictions.getMaxSecurityDepositAsPercent()); Math.min(2 * (max - min) / max, Restrictions.getMaxSecurityDepositPct());
securityDepositPct.set(Math.max(suggestedSecurityDeposit, minSecurityDeposit)); securityDepositPct.set(Math.max(suggestedSecurityDeposit, minSecurityDeposit));
} catch (Throwable t) { } catch (Throwable t) {
log.error(t.toString()); log.error(t.toString());
@ -472,14 +472,14 @@ public abstract class MutableOfferDataModel extends OfferDataModel {
return marketPriceMarginPct; return marketPriceMarginPct;
} }
BigInteger getMaxTradeLimit() {
return offerUtil.getMaxTradeLimitForRelease(paymentAccount, tradeCurrencyCode.get(), direction, buyerAsTakerWithoutDeposit.get());
}
BigInteger getMinTradeLimit() { BigInteger getMinTradeLimit() {
return Restrictions.getMinTradeAmount(); return Restrictions.getMinTradeAmount();
} }
BigInteger getMaxTradeLimit() {
return offerUtil.getMaxTradeLimitForRelease(paymentAccount, tradeCurrencyCode.get(), direction, buyerAsTakerWithoutDeposit.get());
}
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
// Utils // Utils
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
@ -537,10 +537,10 @@ public abstract class MutableOfferDataModel extends OfferDataModel {
// if the volume != amount * price, we need to adjust the amount // if the volume != amount * price, we need to adjust the amount
if (amount.get() == null || !volumeBefore.equals(price.get().getVolumeByAmount(amount.get()))) { if (amount.get() == null || !volumeBefore.equals(price.get().getVolumeByAmount(amount.get()))) {
BigInteger value = price.get().getAmountByVolume(volumeBefore); BigInteger value = price.get().getAmountByVolume(volumeBefore);
BigInteger minAmount = getMinTradeLimit();
BigInteger maxAmount = getMaxTradeLimit(); BigInteger maxAmount = getMaxTradeLimit();
BigInteger minAmount = Restrictions.getMinTradeAmount();
value = value.min(maxAmount); // adjust if above maximum
value = value.max(minAmount); // adjust if below minimum value = value.max(minAmount); // adjust if below minimum
value = value.min(maxAmount); // adjust if above maximum
value = CoinUtil.getRoundedAmount(value, price.get(), minAmount, maxAmount, tradeCurrencyCode.get(), paymentAccount.getPaymentMethod().getId()); value = CoinUtil.getRoundedAmount(value, price.get(), minAmount, maxAmount, tradeCurrencyCode.get(), paymentAccount.getPaymentMethod().getId());
amount.set(value); amount.set(value);
} }
@ -689,7 +689,7 @@ public abstract class MutableOfferDataModel extends OfferDataModel {
double offerSellerSecurityDepositAsPercent = CoinUtil.getAsPercentPerXmr(offerSellerSecurityDeposit, double offerSellerSecurityDepositAsPercent = CoinUtil.getAsPercentPerXmr(offerSellerSecurityDeposit,
offer.getAmount()); offer.getAmount());
return Math.min(offerSellerSecurityDepositAsPercent, return Math.min(offerSellerSecurityDepositAsPercent,
Restrictions.getMaxSecurityDepositAsPercent()); Restrictions.getMaxSecurityDepositPct());
} }
ReadOnlyObjectProperty<BigInteger> totalToPayAsProperty() { ReadOnlyObjectProperty<BigInteger> totalToPayAsProperty() {

View file

@ -483,6 +483,7 @@ public abstract class MutableOfferViewModel<M extends MutableOfferDataModel> ext
buyerAsTakerWithoutDepositListener = (ov, oldValue, newValue) -> { buyerAsTakerWithoutDepositListener = (ov, oldValue, newValue) -> {
if (dataModel.paymentAccount != null) xmrValidator.setMaxValue(dataModel.paymentAccount.getPaymentMethod().getMaxTradeLimit(dataModel.getTradeCurrencyCode().get())); if (dataModel.paymentAccount != null) xmrValidator.setMaxValue(dataModel.paymentAccount.getPaymentMethod().getMaxTradeLimit(dataModel.getTradeCurrencyCode().get()));
xmrValidator.setMaxTradeLimit(dataModel.getMaxTradeLimit()); xmrValidator.setMaxTradeLimit(dataModel.getMaxTradeLimit());
xmrValidator.setMinValue(dataModel.getMinTradeLimit());
if (amount.get() != null) amountValidationResult.set(isXmrInputValid(amount.get())); if (amount.get() != null) amountValidationResult.set(isXmrInputValid(amount.get()));
updateSecurityDeposit(); updateSecurityDeposit();
setSecurityDepositToModel(); setSecurityDepositToModel();
@ -601,7 +602,7 @@ public abstract class MutableOfferViewModel<M extends MutableOfferDataModel> ext
if (dataModel.paymentAccount != null) if (dataModel.paymentAccount != null)
xmrValidator.setMaxValue(dataModel.paymentAccount.getPaymentMethod().getMaxTradeLimit(dataModel.getTradeCurrencyCode().get())); xmrValidator.setMaxValue(dataModel.paymentAccount.getPaymentMethod().getMaxTradeLimit(dataModel.getTradeCurrencyCode().get()));
xmrValidator.setMaxTradeLimit(dataModel.getMaxTradeLimit()); xmrValidator.setMaxTradeLimit(dataModel.getMaxTradeLimit());
xmrValidator.setMinValue(Restrictions.getMinTradeAmount()); xmrValidator.setMinValue(dataModel.getMinTradeLimit());
final boolean isBuy = dataModel.getDirection() == OfferDirection.BUY; final boolean isBuy = dataModel.getDirection() == OfferDirection.BUY;
@ -741,7 +742,7 @@ public abstract class MutableOfferViewModel<M extends MutableOfferDataModel> ext
if (minAmount.get() != null) if (minAmount.get() != null)
minAmountValidationResult.set(isXmrInputValid(minAmount.get())); minAmountValidationResult.set(isXmrInputValid(minAmount.get()));
} else if (amount.get() != null && xmrValidator.getMaxTradeLimit() != null && xmrValidator.getMaxTradeLimit().longValueExact() == OfferRestrictions.TOLERATED_SMALL_TRADE_AMOUNT.longValueExact()) { } else if (amount.get() != null && xmrValidator.getMaxTradeLimit() != null && xmrValidator.getMaxTradeLimit().longValueExact() == OfferRestrictions.TOLERATED_SMALL_TRADE_AMOUNT.longValueExact()) {
if (ParsingUtils.parseNumberStringToDouble(amount.get()) < HavenoUtils.atomicUnitsToXmr(Restrictions.getMinTradeAmount())) { if (ParsingUtils.parseNumberStringToDouble(amount.get()) < HavenoUtils.atomicUnitsToXmr(dataModel.getMinTradeLimit())) {
amountValidationResult.set(result); amountValidationResult.set(result);
} else { } else {
amount.set(HavenoUtils.formatXmr(xmrValidator.getMaxTradeLimit())); amount.set(HavenoUtils.formatXmr(xmrValidator.getMaxTradeLimit()));
@ -980,7 +981,7 @@ public abstract class MutableOfferViewModel<M extends MutableOfferDataModel> ext
InputValidator.ValidationResult result = securityDepositValidator.validate(securityDeposit.get()); InputValidator.ValidationResult result = securityDepositValidator.validate(securityDeposit.get());
securityDepositValidationResult.set(result); securityDepositValidationResult.set(result);
if (result.isValid) { if (result.isValid) {
double defaultSecurityDeposit = Restrictions.getDefaultSecurityDepositAsPercent(); double defaultSecurityDeposit = Restrictions.getDefaultSecurityDepositPct();
String key = "buyerSecurityDepositIsLowerAsDefault"; String key = "buyerSecurityDepositIsLowerAsDefault";
double depositAsDouble = ParsingUtils.parsePercentStringToDouble(securityDeposit.get()); double depositAsDouble = ParsingUtils.parsePercentStringToDouble(securityDeposit.get());
if (preferences.showAgain(key) && depositAsDouble < defaultSecurityDeposit) { if (preferences.showAgain(key) && depositAsDouble < defaultSecurityDeposit) {
@ -1167,10 +1168,9 @@ public abstract class MutableOfferViewModel<M extends MutableOfferDataModel> ext
if (amount.get() != null && !amount.get().isEmpty()) { if (amount.get() != null && !amount.get().isEmpty()) {
BigInteger amount = HavenoUtils.coinToAtomicUnits(DisplayUtils.parseToCoinWith4Decimals(this.amount.get(), xmrFormatter)); BigInteger amount = HavenoUtils.coinToAtomicUnits(DisplayUtils.parseToCoinWith4Decimals(this.amount.get(), xmrFormatter));
BigInteger maxTradeLimit = dataModel.getMaxTradeLimit();
Price price = dataModel.getPrice().get(); Price price = dataModel.getPrice().get();
if (price != null && price.isPositive()) { if (price != null && price.isPositive()) {
amount = CoinUtil.getRoundedAmount(amount, price, dataModel.getMinAmount().get(), maxTradeLimit, tradeCurrencyCode.get(), dataModel.getPaymentAccount().getPaymentMethod().getId()); amount = CoinUtil.getRoundedAmount(amount, price, dataModel.getMinTradeLimit(), dataModel.getMaxTradeLimit(), tradeCurrencyCode.get(), dataModel.getPaymentAccount().getPaymentMethod().getId());
} }
dataModel.setAmount(amount); dataModel.setAmount(amount);
if (syncMinAmountWithAmount || if (syncMinAmountWithAmount ||
@ -1227,7 +1227,7 @@ public abstract class MutableOfferViewModel<M extends MutableOfferDataModel> ext
if (securityDeposit.get() != null && !securityDeposit.get().isEmpty() && !isMinSecurityDeposit.get()) { if (securityDeposit.get() != null && !securityDeposit.get().isEmpty() && !isMinSecurityDeposit.get()) {
dataModel.setSecurityDepositPct(ParsingUtils.parsePercentStringToDouble(securityDeposit.get())); dataModel.setSecurityDepositPct(ParsingUtils.parsePercentStringToDouble(securityDeposit.get()));
} else { } else {
dataModel.setSecurityDepositPct(Restrictions.getDefaultSecurityDepositAsPercent()); dataModel.setSecurityDepositPct(Restrictions.getDefaultSecurityDepositPct());
} }
} }
@ -1243,7 +1243,7 @@ public abstract class MutableOfferViewModel<M extends MutableOfferDataModel> ext
// If the security deposit in the model is not valid percent // If the security deposit in the model is not valid percent
String value = FormattingUtils.formatToPercent(dataModel.getSecurityDepositPct().get()); String value = FormattingUtils.formatToPercent(dataModel.getSecurityDepositPct().get());
if (!securityDepositValidator.validate(value).isValid) { if (!securityDepositValidator.validate(value).isValid) {
dataModel.setSecurityDepositPct(Restrictions.getDefaultSecurityDepositAsPercent()); dataModel.setSecurityDepositPct(Restrictions.getDefaultSecurityDepositPct());
} }
} }
@ -1299,7 +1299,7 @@ public abstract class MutableOfferViewModel<M extends MutableOfferDataModel> ext
} else { } else {
boolean hasBuyerAsTakerWithoutDeposit = dataModel.buyerAsTakerWithoutDeposit.get() && dataModel.isSellOffer(); boolean hasBuyerAsTakerWithoutDeposit = dataModel.buyerAsTakerWithoutDeposit.get() && dataModel.isSellOffer();
securityDeposit.set(FormattingUtils.formatToPercent(hasBuyerAsTakerWithoutDeposit ? securityDeposit.set(FormattingUtils.formatToPercent(hasBuyerAsTakerWithoutDeposit ?
Restrictions.getDefaultSecurityDepositAsPercent() : // use default percent if no deposit from buyer Restrictions.getDefaultSecurityDepositPct() : // use default percent if no deposit from buyer
dataModel.getSecurityDepositPct().get())); dataModel.getSecurityDepositPct().get()));
} }
} }
@ -1333,8 +1333,8 @@ public abstract class MutableOfferViewModel<M extends MutableOfferDataModel> ext
} }
private ValidationResult getExtraInfoValidationResult() { private ValidationResult getExtraInfoValidationResult() {
if (extraInfo.get() != null && !extraInfo.get().isEmpty() && extraInfo.get().length() > Restrictions.MAX_EXTRA_INFO_LENGTH) { if (extraInfo.get() != null && !extraInfo.get().isEmpty() && extraInfo.get().length() > Restrictions.getMaxExtraInfoLength()) {
return new InputValidator.ValidationResult(false, Res.get("createOffer.extraInfo.invalid.tooLong", Restrictions.MAX_EXTRA_INFO_LENGTH)); return new InputValidator.ValidationResult(false, Res.get("createOffer.extraInfo.invalid.tooLong", Restrictions.getMaxExtraInfoLength()));
} else { } else {
return new InputValidator.ValidationResult(true); return new InputValidator.ValidationResult(true);
} }

View file

@ -130,9 +130,9 @@ class EditOfferDataModel extends MutableOfferDataModel {
// by percentage than the restriction. We can't determine the percentage originally entered at offer // by percentage than the restriction. We can't determine the percentage originally entered at offer
// creation, so just use the default value as it doesn't matter anyway. // creation, so just use the default value as it doesn't matter anyway.
double securityDepositPercent = CoinUtil.getAsPercentPerXmr(offer.getMaxSellerSecurityDeposit(), offer.getAmount()); double securityDepositPercent = CoinUtil.getAsPercentPerXmr(offer.getMaxSellerSecurityDeposit(), offer.getAmount());
if (securityDepositPercent > Restrictions.getMaxSecurityDepositAsPercent() if (securityDepositPercent > Restrictions.getMaxSecurityDepositPct()
&& offer.getMaxSellerSecurityDeposit().equals(Restrictions.getMinSecurityDeposit())) && offer.getMaxSellerSecurityDeposit().equals(Restrictions.getMinSecurityDeposit()))
securityDepositPct.set(Restrictions.getDefaultSecurityDepositAsPercent()); securityDepositPct.set(Restrictions.getDefaultSecurityDepositPct());
else else
securityDepositPct.set(securityDepositPercent); securityDepositPct.set(securityDepositPercent);