support goldback (XGB)

This commit is contained in:
woodser 2023-09-03 10:45:55 -04:00
parent 6a49fffb38
commit 9c2308f7a3
23 changed files with 135 additions and 121 deletions

View file

@ -56,6 +56,14 @@ public class CurrencyUtil {
private static String baseCurrencyCode = "XMR";
private static List<TraditionalCurrency> getTraditionalNonFiatCurrencies() {
return Arrays.asList(
new TraditionalCurrency("XAG", "Gold"),
new TraditionalCurrency("XAU", "Silver"),
new TraditionalCurrency("XGB", "Goldback")
);
}
// Calls to isTraditionalCurrency and isCryptoCurrency are very frequent so we use a cache of the results.
// The main improvement was already achieved with using memoize for the source maps, but
// the caching still reduces performance costs by about 20% for isCryptoCurrency (1752 ms vs 2121 ms) and about 50%
@ -101,7 +109,7 @@ public class CurrencyUtil {
public static Collection<TraditionalCurrency> getAllSortedTraditionalCurrencies(Comparator comparator) {
return (List<TraditionalCurrency>) getAllSortedTraditionalCurrencies().stream()
.sorted(comparator) // sorted by comparator param
.sorted(comparator)
.collect(Collectors.toList());
}
@ -109,7 +117,7 @@ public class CurrencyUtil {
List<TraditionalCurrency> currencies = CountryUtil.getAllCountries().stream()
.map(country -> getCurrencyByCountryCode(country.code))
.collect(Collectors.toList());
for (String isoCode : nonFiatIsoCodes) currencies.add(new TraditionalCurrency(Currency.getInstance(isoCode)));
currencies.addAll(getTraditionalNonFiatCurrencies());
return currencies.stream().sorted(TradeCurrency::compareTo)
.distinct()
.collect(Collectors.toMap(TradeCurrency::getCode, Function.identity(), (x, y) -> x, LinkedHashMap::new));
@ -131,12 +139,14 @@ public class CurrencyUtil {
public static List<TraditionalCurrency> getMainTraditionalCurrencies() {
List<TraditionalCurrency> list = getMainFiatCurrencies();
for (String isoCode : nonFiatIsoCodes) list.add(new TraditionalCurrency(isoCode));
list.addAll(getTraditionalNonFiatCurrencies());
postProcessTraditionalCurrenciesList(list);
return list;
}
private static List<String> nonFiatIsoCodes = Arrays.asList("XAG", "XAU");
private static boolean isTraditionalNonFiatCurrency(String currencyCode) {
return getTraditionalNonFiatCurrencies().stream().anyMatch(c -> c.getCode().equals(currencyCode));
}
private static void postProcessTraditionalCurrenciesList(List<TraditionalCurrency> list) {
list.sort(TradeCurrency::compareTo);
@ -209,7 +219,7 @@ public class CurrencyUtil {
public static boolean isFiatCurrency(String currencyCode) {
if (!isTraditionalCurrency(currencyCode)) return false;
if ("XAG".equalsIgnoreCase(currencyCode) || "XAU".equalsIgnoreCase(currencyCode)) return false;
if (isTraditionalNonFiatCurrency(currencyCode)) return false;
return true;
}
@ -224,7 +234,7 @@ public class CurrencyUtil {
boolean isTraditionalCurrency = currencyCode != null
&& !currencyCode.isEmpty()
&& !isCryptoCurrency(currencyCode)
&& Currency.getInstance(currencyCode) != null;
&& (isTraditionalNonFiatCurrency(currencyCode) || Currency.getInstance(currencyCode) != null);
if (currencyCode != null) {
isTraditionalCurrencyMap.put(currencyCode, isTraditionalCurrency);
@ -237,6 +247,17 @@ public class CurrencyUtil {
}
}
public static boolean isVolumeRoundedToNearestUnit(String currencyCode) {
return isFiatCurrency(currencyCode) ||
"XGB".equals(currencyCode.toUpperCase());
}
public static boolean isPricePrecise(String currencyCode) {
return isCryptoCurrency(currencyCode) ||
"XAU".equals(currencyCode.toUpperCase()) ||
"XAG".equals(currencyCode.toUpperCase());
}
public static Optional<TraditionalCurrency> getTraditionalCurrency(String currencyCode) {
return Optional.ofNullable(traditionalCurrencyMapSupplier.get().get(currencyCode));
}

View file

@ -30,15 +30,22 @@ import java.util.Locale;
@ToString
@Getter
public final class TraditionalCurrency extends TradeCurrency {
// http://boschista.deviantart.com/journal/Cool-ASCII-Symbols-214218618
private final static String PREFIX = "";
private final Currency currency;
public TraditionalCurrency(String currencyCode) {
this(Currency.getInstance(currencyCode), getLocale());
}
public TraditionalCurrency(String currencyCode, String name) {
super(currencyCode, name);
}
public TraditionalCurrency(TraditionalCurrency currency) {
this(currency.getCode(), currency.getName());
}
@SuppressWarnings("WeakerAccess")
public TraditionalCurrency(Currency currency) {
this(currency, getLocale());
@ -47,7 +54,6 @@ public final class TraditionalCurrency extends TradeCurrency {
@SuppressWarnings("WeakerAccess")
public TraditionalCurrency(Currency currency, Locale locale) {
super(currency.getCurrencyCode(), currency.getDisplayName(locale));
this.currency = currency;
}
@ -57,15 +63,15 @@ public final class TraditionalCurrency extends TradeCurrency {
@Override
public Message toProtoMessage() {
protobuf.Currency.Builder currencyBuilder = protobuf.Currency.newBuilder().setCurrencyCode(currency.getCurrencyCode());
protobuf.TraditionalCurrency.Builder traditionalCurrencyBuilder = protobuf.TraditionalCurrency.newBuilder().setCurrency(currencyBuilder);
return getTradeCurrencyBuilder()
.setTraditionalCurrency(traditionalCurrencyBuilder)
.setCode(code)
.setName(name)
.setTraditionalCurrency(protobuf.TraditionalCurrency.newBuilder())
.build();
}
public static TraditionalCurrency fromProto(protobuf.TradeCurrency proto) {
return new TraditionalCurrency(proto.getCode());
return new TraditionalCurrency(proto.getCode(), proto.getName());
}

View file

@ -632,7 +632,7 @@ public final class Preferences implements PersistedDataHost, BridgeAddressProvid
public void setTraditionalCurrencies(List<TraditionalCurrency> currencies) {
traditionalCurrenciesAsObservable.setAll(currencies.stream()
.map(traditionalCurrency -> new TraditionalCurrency(traditionalCurrency.getCurrency()))
.map(traditionalCurrency -> new TraditionalCurrency(traditionalCurrency))
.distinct().collect(Collectors.toList()));
requestPersistence();
}

View file

@ -29,9 +29,8 @@ public class FormattingUtils {
public final static String RANGE_SEPARATOR = " - ";
private static final MonetaryFormat fiatPriceFormat = new MonetaryFormat().shift(0).minDecimals(4).repeatOptionalDecimals(0, 0);
private static final MonetaryFormat nonFiatPriceFormat = new MonetaryFormat().shift(0).minDecimals(8).repeatOptionalDecimals(0, 0);
private static final MonetaryFormat traditionalFormat = new MonetaryFormat().shift(0).minDecimals(TraditionalMoney.SMALLEST_UNIT_EXPONENT).repeatOptionalDecimals(0, 0);
private static final MonetaryFormat priceFormat4Decimals = new MonetaryFormat().shift(0).minDecimals(4).repeatOptionalDecimals(0, 0);
private static final MonetaryFormat priceFormat8Decimals = new MonetaryFormat().shift(0).minDecimals(8).repeatOptionalDecimals(0, 0);
private static final MonetaryFormat cryptoFormat = new MonetaryFormat().shift(0).minDecimals(CryptoMoney.SMALLEST_UNIT_EXPONENT).repeatOptionalDecimals(0, 0);
private static final DecimalFormat decimalFormat = new DecimalFormat("#.#");
@ -293,11 +292,7 @@ public class FormattingUtils {
return formattedNumber;
}
public static MonetaryFormat getMonetaryFormat(String currencyCode) {
return CurrencyUtil.isTraditionalCurrency(currencyCode) ? traditionalFormat : cryptoFormat;
}
public static MonetaryFormat getPriceMonetaryFormat(String currencyCode) {
return CurrencyUtil.isFiatCurrency(currencyCode) ? fiatPriceFormat : nonFiatPriceFormat;
return CurrencyUtil.isPricePrecise(currencyCode) ? priceFormat8Decimals : priceFormat4Decimals;
}
}

View file

@ -29,8 +29,8 @@ import haveno.core.provider.price.MarketPrice;
import haveno.core.provider.price.PriceFeedService;
import haveno.core.trade.statistics.TradeStatisticsManager;
import haveno.core.user.Preferences;
import haveno.core.util.validation.NonFiatPriceValidator;
import haveno.core.util.validation.FiatPriceValidator;
import haveno.core.util.validation.AmountValidator8Decimals;
import haveno.core.util.validation.AmountValidator4Decimals;
import haveno.core.util.validation.InputValidator;
import haveno.core.util.validation.MonetaryValidator;
import lombok.extern.slf4j.Slf4j;
@ -53,21 +53,21 @@ public class PriceUtil {
this.priceFeedService = priceFeedService;
}
public static MonetaryValidator getPriceValidator(boolean isFiatCurrency) {
return isFiatCurrency ?
new FiatPriceValidator() :
new NonFiatPriceValidator();
public static MonetaryValidator getPriceValidator(String currencyCode) {
return CurrencyUtil.isPricePrecise(currencyCode) ?
new AmountValidator4Decimals() :
new AmountValidator8Decimals();
}
public static InputValidator.ValidationResult isTriggerPriceValid(String triggerPriceAsString,
MarketPrice marketPrice,
boolean isSellOffer,
boolean isFiatCurrency) {
String currencyCode) {
if (triggerPriceAsString == null || triggerPriceAsString.isEmpty()) {
return new InputValidator.ValidationResult(true);
}
InputValidator.ValidationResult result = getPriceValidator(isFiatCurrency).validate(triggerPriceAsString);
InputValidator.ValidationResult result = getPriceValidator(currencyCode).validate(triggerPriceAsString);
if (!result.isValid) {
return result;
}
@ -76,7 +76,8 @@ public class PriceUtil {
long marketPriceAsLong = PriceUtil.getMarketPriceAsLong("" + marketPrice.getPrice(), marketPrice.getCurrencyCode());
String marketPriceAsString = FormattingUtils.formatMarketPrice(marketPrice.getPrice(), marketPrice.getCurrencyCode());
if ((isSellOffer && isFiatCurrency) || (!isSellOffer && !isFiatCurrency)) {
boolean isCryptoCurrency = CurrencyUtil.isCryptoCurrency(currencyCode);
if ((isSellOffer && !isCryptoCurrency) || (!isSellOffer && isCryptoCurrency)) {
if (triggerPriceAsLong >= marketPriceAsLong) {
return new InputValidator.ValidationResult(false,
Res.get("createOffer.triggerPrice.invalid.tooHigh", marketPriceAsString));

View file

@ -38,33 +38,33 @@ import java.util.Locale;
public class VolumeUtil {
private static final MonetaryFormat FIAT_VOLUME_FORMAT = new MonetaryFormat().shift(0).minDecimals(0).repeatOptionalDecimals(0, 0);
private static final MonetaryFormat TRADITIONAL_VOLUME_FORMAT = new MonetaryFormat().shift(0).minDecimals(4).repeatOptionalDecimals(0, 0);
private static final MonetaryFormat VOLUME_FORMAT_UNIT = new MonetaryFormat().shift(0).minDecimals(0).repeatOptionalDecimals(0, 0);
private static final MonetaryFormat VOLUME_FORMAT_PRECISE = new MonetaryFormat().shift(0).minDecimals(4).repeatOptionalDecimals(0, 0);
private static double EXPONENT = Math.pow(10, TraditionalMoney.SMALLEST_UNIT_EXPONENT); // 1000000000000 with precision 8
public static Volume getAdjustedVolume(Volume volumeByAmount, String paymentMethodId) {
if (PaymentMethod.isRoundedForAtmCash(paymentMethodId))
return VolumeUtil.getRoundedAtmCashVolume(volumeByAmount);
else if (CurrencyUtil.isFiatCurrency(volumeByAmount.getCurrencyCode()))
return VolumeUtil.getRoundedFiatVolume(volumeByAmount);
else if (CurrencyUtil.isVolumeRoundedToNearestUnit(volumeByAmount.getCurrencyCode()))
return VolumeUtil.getRoundedVolumeUnit(volumeByAmount);
else if (CurrencyUtil.isTraditionalCurrency(volumeByAmount.getCurrencyCode()))
return VolumeUtil.getRoundedTraditionalVolume(volumeByAmount);
return VolumeUtil.getRoundedVolumePrecise(volumeByAmount);
return volumeByAmount;
}
public static Volume getRoundedFiatVolume(Volume volumeByAmount) {
// We want to get rounded to 1 unit of the fiat currency, e.g. 1 EUR.
return getAdjustedFiatVolume(volumeByAmount, 1);
public static Volume getRoundedVolumeUnit(Volume volumeByAmount) {
// We want to get rounded to 1 unit of the currency, e.g. 1 EUR.
return getAdjustedVolumeUnit(volumeByAmount, 1);
}
private static Volume getRoundedAtmCashVolume(Volume volumeByAmount) {
// EUR has precision TraditionalMoney.SMALLEST_UNIT_EXPONENT and we want multiple of 10 so we divide by EXPONENT then
// round and multiply with 10
return getAdjustedFiatVolume(volumeByAmount, 10);
return getAdjustedVolumeUnit(volumeByAmount, 10);
}
public static Volume getRoundedTraditionalVolume(Volume volumeByAmount) {
public static Volume getRoundedVolumePrecise(Volume volumeByAmount) {
DecimalFormat decimalFormat = new DecimalFormat("#.####");
double roundedVolume = Double.parseDouble(decimalFormat.format(Double.parseDouble(volumeByAmount.toString())));
return Volume.parse(String.valueOf(roundedVolume), volumeByAmount.getCurrencyCode());
@ -77,7 +77,7 @@ public class VolumeUtil {
* units of 1 EUR, 10 means rounded to 10 EUR.
* @return The adjusted Fiat volume
*/
public static Volume getAdjustedFiatVolume(Volume volumeByAmount, int factor) {
public static Volume getAdjustedVolumeUnit(Volume volumeByAmount, int factor) {
// Fiat currencies use precision TraditionalMoney.SMALLEST_UNIT_EXPONENT and we want multiple of factor so we divide
// by EXPONENT * factor then round and multiply with factor
long roundedVolume = Math.round((double) volumeByAmount.getValue() / (EXPONENT * factor)) * factor;
@ -168,6 +168,6 @@ public class VolumeUtil {
}
private static MonetaryFormat getMonetaryFormat(String currencyCode) {
return CurrencyUtil.isFiatCurrency(currencyCode) ? FIAT_VOLUME_FORMAT : TRADITIONAL_VOLUME_FORMAT;
return CurrencyUtil.isVolumeRoundedToNearestUnit(currencyCode) ? VOLUME_FORMAT_UNIT : VOLUME_FORMAT_PRECISE;
}
}

View file

@ -32,7 +32,7 @@ import java.math.BigInteger;
import java.text.DecimalFormat;
import static com.google.common.base.Preconditions.checkArgument;
import static haveno.core.util.VolumeUtil.getAdjustedFiatVolume;
import static haveno.core.util.VolumeUtil.getAdjustedVolumeUnit;
public class CoinUtil {
@ -82,10 +82,10 @@ public class CoinUtil {
public static BigInteger getRoundedAmount(BigInteger amount, Price price, long maxTradeLimit, String currencyCode, String paymentMethodId) {
if (PaymentMethod.isRoundedForAtmCash(paymentMethodId)) {
return getRoundedAtmCashAmount(amount, price, maxTradeLimit);
} else if (CurrencyUtil.isFiatCurrency(currencyCode)) {
return getRoundedFiatAmount(amount, price, maxTradeLimit);
} else if (CurrencyUtil.isVolumeRoundedToNearestUnit(currencyCode)) {
return getRoundedAmountUnit(amount, price, maxTradeLimit);
} else if (CurrencyUtil.isTraditionalCurrency(currencyCode)) {
return getRoundedTraditionalAmount(amount, price, maxTradeLimit);
return getRoundedAmountPrecise(amount, price, maxTradeLimit);
}
return amount;
}
@ -98,16 +98,16 @@ public class CoinUtil {
* Calculate the possibly adjusted amount for {@code amount}, taking into account the
* {@code price} and {@code maxTradeLimit} and {@code factor}.
*
* @param amount Bitcoin amount which is a candidate for getting rounded.
* @param amount Monero amount which is a candidate for getting rounded.
* @param price Price used in relation to that amount.
* @param maxTradeLimit The max. trade limit of the users account, in satoshis.
* @param maxTradeLimit The max. trade limit of the users account, in atomic units.
* @return The adjusted amount
*/
public static BigInteger getRoundedFiatAmount(BigInteger amount, Price price, long maxTradeLimit) {
public static BigInteger getRoundedAmountUnit(BigInteger amount, Price price, long maxTradeLimit) {
return getAdjustedAmount(amount, price, maxTradeLimit, 1);
}
public static BigInteger getRoundedTraditionalAmount(BigInteger amount, Price price, long maxTradeLimit) {
public static BigInteger getRoundedAmountPrecise(BigInteger amount, Price price, long maxTradeLimit) {
DecimalFormat decimalFormat = new DecimalFormat("#.####");
double roundedXmrAmount = Double.parseDouble(decimalFormat.format(HavenoUtils.atomicUnitsToXmr(amount)));
return HavenoUtils.xmrToAtomicUnits(roundedXmrAmount);
@ -154,8 +154,8 @@ public class CoinUtil {
// We get the adjusted volume from our amount
Volume volume = useSmallestUnitForAmount
? getAdjustedFiatVolume(price.getVolumeByAmount(smallestUnitForAmount), factor)
: getAdjustedFiatVolume(price.getVolumeByAmount(amount), factor);
? getAdjustedVolumeUnit(price.getVolumeByAmount(smallestUnitForAmount), factor)
: getAdjustedVolumeUnit(price.getVolumeByAmount(amount), factor);
if (volume.getValue() <= 0)
return BigInteger.valueOf(0);

View file

@ -19,7 +19,7 @@ package haveno.core.util.validation;
import javax.inject.Inject;
public class FiatPriceValidator extends MonetaryValidator {
public class AmountValidator4Decimals extends MonetaryValidator {
@Override
public double getMinValue() {
return 0.0001;
@ -33,6 +33,6 @@ public class FiatPriceValidator extends MonetaryValidator {
}
@Inject
public FiatPriceValidator() {
public AmountValidator4Decimals() {
}
}

View file

@ -19,7 +19,7 @@ package haveno.core.util.validation;
import javax.inject.Inject;
public class NonFiatPriceValidator extends MonetaryValidator {
public class AmountValidator8Decimals extends MonetaryValidator {
@Override
public double getMinValue() {
return 0.00000001;
@ -32,6 +32,6 @@ public class NonFiatPriceValidator extends MonetaryValidator {
}
@Inject
public NonFiatPriceValidator() {
public AmountValidator8Decimals() {
}
}