mirror of
https://github.com/boldsuck/haveno.git
synced 2024-12-22 20:19:21 +00:00
limit sell offers to unsigned buy limit then warn within release windows
This commit is contained in:
parent
a63118d5eb
commit
f91f213cd2
8 changed files with 116 additions and 15 deletions
|
@ -433,10 +433,12 @@ public class AccountAgeWitnessService {
|
|||
limit = BigInteger.valueOf(MathUtils.roundDoubleToLong(maxTradeLimit.longValueExact() * factor));
|
||||
}
|
||||
|
||||
if (accountAgeWitness != null) {
|
||||
log.debug("limit={}, factor={}, accountAgeWitnessHash={}",
|
||||
limit,
|
||||
factor,
|
||||
Utilities.bytesAsHexString(accountAgeWitness.getHash()));
|
||||
}
|
||||
return limit;
|
||||
}
|
||||
|
||||
|
@ -518,6 +520,15 @@ public class AccountAgeWitnessService {
|
|||
paymentAccount.getPaymentMethod()).longValueExact();
|
||||
}
|
||||
|
||||
public long getUnsignedTradeLimit(PaymentMethod paymentMethod, String currencyCode, OfferDirection direction) {
|
||||
return getTradeLimit(paymentMethod.getMaxTradeLimit(currencyCode),
|
||||
currencyCode,
|
||||
null,
|
||||
AccountAge.UNVERIFIED,
|
||||
direction,
|
||||
paymentMethod).longValueExact();
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Verification
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
|
|
@ -44,6 +44,8 @@ import java.security.PrivateKey;
|
|||
import java.text.DecimalFormat;
|
||||
import java.text.DecimalFormatSymbols;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Calendar;
|
||||
import java.util.Date;
|
||||
import java.util.Locale;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import javax.annotation.Nullable;
|
||||
|
@ -60,8 +62,13 @@ import org.bitcoinj.core.Coin;
|
|||
@Slf4j
|
||||
public class HavenoUtils {
|
||||
|
||||
// Use the US locale as a base for all DecimalFormats (commas should be omitted from number strings).
|
||||
public static final DecimalFormatSymbols DECIMAL_FORMAT_SYMBOLS = DecimalFormatSymbols.getInstance(Locale.US);
|
||||
// configurable
|
||||
private static final String RELEASE_DATE = "01-03-2024 00:00:00"; // optionally set to release date of the network in format dd-mm-yyyy to impose temporary limits, etc. e.g. "01-03-2024 00:00:00"
|
||||
public static final int RELEASE_LIMIT_DAYS = 60; // number of days to limit sell offers to max buy limit for new accounts
|
||||
public static final int WARN_ON_OFFER_EXCEEDS_UNSIGNED_BUY_LIMIT_DAYS = 182; // number of days to warn if sell offer exceeds unsigned buy limit
|
||||
|
||||
// non-configurable
|
||||
public static final DecimalFormatSymbols DECIMAL_FORMAT_SYMBOLS = DecimalFormatSymbols.getInstance(Locale.US); // use the US locale as a base for all DecimalFormats (commas should be omitted from number strings)
|
||||
public static int XMR_SMALLEST_UNIT_EXPONENT = 12;
|
||||
public static final String LOOPBACK_HOST = "127.0.0.1"; // local loopback address to host Monero node
|
||||
public static final String LOCALHOST = "localhost";
|
||||
|
@ -70,14 +77,34 @@ public class HavenoUtils {
|
|||
public static final DecimalFormat XMR_FORMATTER = new DecimalFormat("##############0.000000000000", DECIMAL_FORMAT_SYMBOLS);
|
||||
public static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("dd-MM-yyyy HH:mm:ss");
|
||||
|
||||
// TODO: better way to share references?
|
||||
public static ArbitrationManager arbitrationManager;
|
||||
public static ArbitrationManager arbitrationManager; // TODO: better way to share references?
|
||||
public static HavenoSetup havenoSetup;
|
||||
|
||||
public static boolean isSeedNode() {
|
||||
return havenoSetup == null;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public static Date getReleaseDate() {
|
||||
if (RELEASE_DATE == null) return null;
|
||||
try {
|
||||
return DATE_FORMAT.parse(RELEASE_DATE);
|
||||
} catch (Exception e) {
|
||||
log.error("Failed to parse release date: " + RELEASE_DATE, e);
|
||||
throw new IllegalArgumentException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean isReleasedWithinDays(int days) {
|
||||
Date releaseDate = getReleaseDate();
|
||||
if (releaseDate == null) return false;
|
||||
Calendar calendar = Calendar.getInstance();
|
||||
calendar.setTime(releaseDate);
|
||||
calendar.add(Calendar.DATE, days);
|
||||
Date releaseDatePlusDays = calendar.getTime();
|
||||
return new Date().before(releaseDatePlusDays);
|
||||
}
|
||||
|
||||
// ----------------------- CONVERSION UTILS -------------------------------
|
||||
|
||||
public static BigInteger coinToAtomicUnits(Coin coin) {
|
||||
|
|
|
@ -412,6 +412,8 @@ popup.warning.tradeLimitDueAccountAgeRestriction.seller=The allowed trade amount
|
|||
- The buyer''s account has not been signed by an arbitrator or a peer\n\
|
||||
- The time since signing of the buyer''s account is not at least 30 days\n\
|
||||
- The payment method for this offer is considered risky for bank chargebacks\n\n{1}
|
||||
popup.warning.tradeLimitDueAccountAgeRestriction.seller.releaseLimit=This payment method is temporarily limited to {0} until {1} because all buyers have new accounts.\n\n{2}
|
||||
popup.warning.tradeLimitDueAccountAgeRestriction.seller.exceedsUnsignedBuyLimit=Your offer will be limited to buyers with signed and aged accounts because it exceeds {0}.\n\n{1}
|
||||
popup.warning.tradeLimitDueAccountAgeRestriction.buyer=The allowed trade amount is limited to {0} because of security restrictions based on the following criteria:\n\
|
||||
- Your account has not been signed by an arbitrator or a peer\n\
|
||||
- The time since signing of your account is not at least 30 days\n\
|
||||
|
|
|
@ -380,7 +380,7 @@ public class HavenoApp extends Application implements UncaughtExceptionHandler {
|
|||
// if no warning popup has been shown yet, prompt user if they really intend to shut down
|
||||
String key = "popup.info.shutDownQuery";
|
||||
if (injector.getInstance(Preferences.class).showAgain(key) && !DevEnv.isDevMode()) {
|
||||
new Popup().headLine(Res.get("popup.info.shutDownQuery"))
|
||||
new Popup().headLine(Res.get(key))
|
||||
.actionButtonText(Res.get("shared.yes"))
|
||||
.onAction(() -> resp.complete(true))
|
||||
.closeButtonText(Res.get("shared.no"))
|
||||
|
|
|
@ -455,6 +455,12 @@ public abstract class MutableOfferDataModel extends OfferDataModel {
|
|||
}
|
||||
|
||||
long getMaxTradeLimit() {
|
||||
|
||||
// disallow offers which no buyer can take due to trade limits on release
|
||||
if (HavenoUtils.isReleasedWithinDays(HavenoUtils.RELEASE_LIMIT_DAYS)) {
|
||||
return accountAgeWitnessService.getMyTradeLimit(paymentAccount, tradeCurrencyCode.get(), OfferDirection.BUY);
|
||||
}
|
||||
|
||||
if (paymentAccount != null) {
|
||||
return accountAgeWitnessService.getMyTradeLimit(paymentAccount, tradeCurrencyCode.get(), direction);
|
||||
} else {
|
||||
|
@ -586,6 +592,10 @@ public abstract class MutableOfferDataModel extends OfferDataModel {
|
|||
// Getters
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
public BigInteger getMaxUnsignedBuyLimit() {
|
||||
return BigInteger.valueOf(accountAgeWitnessService.getUnsignedTradeLimit(paymentAccount.getPaymentMethod(), tradeCurrencyCode.get(), OfferDirection.BUY));
|
||||
}
|
||||
|
||||
protected ReadOnlyObjectProperty<BigInteger> getAmount() {
|
||||
return amount;
|
||||
}
|
||||
|
|
|
@ -1012,8 +1012,25 @@ public abstract class MutableOfferView<M extends MutableOfferViewModel<?>> exten
|
|||
|
||||
nextButton.setOnAction(e -> {
|
||||
if (model.isPriceInRange()) {
|
||||
|
||||
// warn if sell offer exceeds unsigned buy limit within release window
|
||||
boolean isSellOffer = model.getDataModel().isSellOffer();
|
||||
boolean exceedsUnsignedBuyLimit = model.getDataModel().getAmount().get().compareTo(model.getDataModel().getMaxUnsignedBuyLimit()) > 0;
|
||||
String key = "popup.warning.tradeLimitDueAccountAgeRestriction.seller.exceedsUnsignedBuyLimit";
|
||||
if (isSellOffer && exceedsUnsignedBuyLimit && DontShowAgainLookup.showAgain(key) && HavenoUtils.isReleasedWithinDays(HavenoUtils.WARN_ON_OFFER_EXCEEDS_UNSIGNED_BUY_LIMIT_DAYS)) {
|
||||
new Popup().information(Res.get(key,
|
||||
HavenoUtils.formatXmr(model.getDataModel().getMaxUnsignedBuyLimit(), true),
|
||||
Res.get("offerbook.warning.newVersionAnnouncement")))
|
||||
.closeButtonText(Res.get("shared.cancel"))
|
||||
.actionButtonText(Res.get("shared.ok"))
|
||||
.onAction(this::onShowPayFundsScreen)
|
||||
.width(900)
|
||||
.dontShowAgainId(key)
|
||||
.show();
|
||||
} else {
|
||||
onShowPayFundsScreen();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -81,6 +81,9 @@ import org.bitcoinj.core.Coin;
|
|||
import javax.inject.Inject;
|
||||
import javax.inject.Named;
|
||||
import java.math.BigInteger;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Calendar;
|
||||
import java.util.Date;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import static javafx.beans.binding.Bindings.createStringBinding;
|
||||
|
@ -692,6 +695,26 @@ public abstract class MutableOfferViewModel<M extends MutableOfferDataModel> ext
|
|||
} else {
|
||||
amount.set(HavenoUtils.formatXmr(xmrValidator.getMaxTradeLimit()));
|
||||
boolean isBuy = dataModel.getDirection() == OfferDirection.BUY;
|
||||
boolean isSellerWithinReleaseWindow = !isBuy && HavenoUtils.isReleasedWithinDays(HavenoUtils.RELEASE_LIMIT_DAYS);
|
||||
if (isSellerWithinReleaseWindow) {
|
||||
|
||||
// format release date plus days
|
||||
Date releaseDate = HavenoUtils.getReleaseDate();
|
||||
Calendar c = Calendar.getInstance();
|
||||
c.setTime(releaseDate);
|
||||
c.add(Calendar.DATE, HavenoUtils.RELEASE_LIMIT_DAYS);
|
||||
Date releaseDatePlusDays = c.getTime();
|
||||
SimpleDateFormat formatter = new SimpleDateFormat("MMMM d, yyyy");
|
||||
String releaseDatePlusDaysAsString = formatter.format(releaseDatePlusDays);
|
||||
|
||||
// popup temporary restriction
|
||||
new Popup().information(Res.get("popup.warning.tradeLimitDueAccountAgeRestriction.seller.releaseLimit",
|
||||
HavenoUtils.formatXmr(OfferRestrictions.TOLERATED_SMALL_TRADE_AMOUNT, true),
|
||||
releaseDatePlusDaysAsString,
|
||||
Res.get("offerbook.warning.newVersionAnnouncement")))
|
||||
.width(900)
|
||||
.show();
|
||||
} else {
|
||||
new Popup().information(Res.get(isBuy ? "popup.warning.tradeLimitDueAccountAgeRestriction.buyer" : "popup.warning.tradeLimitDueAccountAgeRestriction.seller",
|
||||
HavenoUtils.formatXmr(OfferRestrictions.TOLERATED_SMALL_TRADE_AMOUNT, true),
|
||||
Res.get("offerbook.warning.newVersionAnnouncement")))
|
||||
|
@ -699,6 +722,7 @@ public abstract class MutableOfferViewModel<M extends MutableOfferDataModel> ext
|
|||
.show();
|
||||
}
|
||||
}
|
||||
}
|
||||
// We want to trigger a recalculation of the volume
|
||||
UserThread.execute(() -> {
|
||||
onFocusOutVolumeTextField(true, false);
|
||||
|
|
|
@ -78,6 +78,16 @@ Keypairs with alert privileges are able to send alerts, e.g. to update the appli
|
|||
|
||||
Set the XMR address to collect trade fees in `getTradeFeeAddress()` in HavenoUtils.java.
|
||||
|
||||
## Set the network's release date
|
||||
|
||||
Optionally set the network's approximate release date by setting `RELEASE_DATE` in HavenoUtils.java.
|
||||
|
||||
This will prevent posting sell offers which no buyers can take before any buyer accounts are signed and aged, while the network bootstraps.
|
||||
|
||||
After a period (default 60 days), the limit is lifted and sellers can post offers exceeding unsigned buy limits, but they will receive an informational warning for an additional period (default 6 months after release).
|
||||
|
||||
The defaults can be adjusted with the related constants in HavenoUtils.java.
|
||||
|
||||
## Create and register arbitrators
|
||||
|
||||
Before running the arbitrator, remember that at least one seednode should already be deployed and its address listed in `core/src/main/resources/xmr_<network>.seednodes`.
|
||||
|
|
Loading…
Reference in a new issue