mirror of
https://github.com/bisq-network/bisq.git
synced 2025-02-22 22:45:21 +01:00
Show latest trade price as market price for currencies which have no external price feed provided but have been traded in Bisq
This commit is contained in:
parent
03c33964a7
commit
1857cc0b30
13 changed files with 102 additions and 39 deletions
|
@ -210,6 +210,9 @@ mainView.menu.account=Account
|
|||
mainView.menu.dao=DAO
|
||||
|
||||
mainView.marketPrice.provider=Market price provider:
|
||||
mainView.marketPrice.bisqInternalPrice=Price of latest Bisq trade
|
||||
mainView.marketPrice.tooltip.bisqInternalPrice=There is no market price from external price feed providers available.\n\
|
||||
The displayed price is the latest Bisq trade price for that currency.
|
||||
mainView.marketPrice.tooltip=Market price is provided by {0}{1}\nLast update: {2}\nProvider node URL: {3}
|
||||
mainView.marketPrice.tooltip.altcoinExtra=If the altcoin is not available at Poloniex we use https://coinmarketcap.com
|
||||
mainView.balance.available=Available balance
|
||||
|
|
|
@ -85,7 +85,7 @@ public class BsqLiteNode extends BsqNode {
|
|||
@Override
|
||||
public void onBlockReceived(GetBsqBlocksResponse getBsqBlocksResponse) {
|
||||
List<BsqBlock> bsqBlockList = new ArrayList<>(getBsqBlocksResponse.getBsqBlocks());
|
||||
log.info("received msg with {} items", bsqBlockList.size(), bsqBlockList.get(bsqBlockList.size() - 1).getHeight());
|
||||
log.info("received msg with {} items", bsqBlockList.size());
|
||||
if (bsqBlockList.size() > 0)
|
||||
log.info("block height of last item: {}", bsqBlockList.get(bsqBlockList.size() - 1).getHeight());
|
||||
// Be safe and reset all mutable data in case the provider would not have done it
|
||||
|
|
|
@ -128,7 +128,7 @@ public class Offer implements NetworkPayload, PersistablePayload {
|
|||
if (offerPayload.isUseMarketBasedPrice()) {
|
||||
checkNotNull(priceFeedService, "priceFeed must not be null");
|
||||
MarketPrice marketPrice = priceFeedService.getMarketPrice(currencyCode);
|
||||
if (marketPrice != null && marketPrice.isValid()) {
|
||||
if (marketPrice != null && marketPrice.isRecentExternalPriceAvailable()) {
|
||||
double factor;
|
||||
double marketPriceMargin = offerPayload.getMarketPriceMargin();
|
||||
if (CurrencyUtil.isCryptoCurrency(currencyCode)) {
|
||||
|
|
|
@ -27,15 +27,24 @@ public class MarketPrice {
|
|||
private final String currencyCode;
|
||||
private final double price;
|
||||
private final long timestampSec;
|
||||
private final boolean isExternallyProvidedPrice; // if we get it from btc average or others.
|
||||
|
||||
public MarketPrice(String currencyCode, double price, long timestampSec) {
|
||||
public MarketPrice(String currencyCode, double price, long timestampSec, boolean isExternallyProvidedPrice) {
|
||||
this.currencyCode = currencyCode;
|
||||
this.price = price;
|
||||
this.timestampSec = timestampSec;
|
||||
this.isExternallyProvidedPrice = isExternallyProvidedPrice;
|
||||
}
|
||||
|
||||
public boolean isValid() {
|
||||
long limit = Instant.now().getEpochSecond() - MARKET_PRICE_MAX_AGE_SEC;
|
||||
return timestampSec > limit && price > 0;
|
||||
public boolean isPriceAvailable() {
|
||||
return price > 0;
|
||||
}
|
||||
|
||||
private boolean isRecentPriceAvailable() {
|
||||
return timestampSec > (Instant.now().getEpochSecond() - MARKET_PRICE_MAX_AGE_SEC) && isPriceAvailable();
|
||||
}
|
||||
|
||||
public boolean isRecentExternalPriceAvailable() {
|
||||
return isExternallyProvidedPrice && isRecentPriceAvailable();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -25,6 +25,8 @@ import io.bisq.common.app.Log;
|
|||
import io.bisq.common.handlers.FaultHandler;
|
||||
import io.bisq.common.locale.CurrencyUtil;
|
||||
import io.bisq.common.locale.TradeCurrency;
|
||||
import io.bisq.common.monetary.Price;
|
||||
import io.bisq.common.util.MathUtils;
|
||||
import io.bisq.common.util.Tuple2;
|
||||
import io.bisq.core.app.BisqEnvironment;
|
||||
import io.bisq.core.provider.ProvidersRepository;
|
||||
|
@ -135,10 +137,21 @@ public class PriceFeedService {
|
|||
public MarketPrice getMarketPrice(String currencyCode) {
|
||||
if (cache.containsKey(currencyCode))
|
||||
return cache.get(currencyCode);
|
||||
else
|
||||
else
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
public void setBisqMarketPrice(String currencyCode, Price price) {
|
||||
if (!cache.containsKey(currencyCode) || !cache.get(currencyCode).isExternallyProvidedPrice()) {
|
||||
cache.put(currencyCode, new MarketPrice(currencyCode,
|
||||
MathUtils.scaleDownByPowerOf10(price.getValue(), CurrencyUtil.isCryptoCurrency(currencyCode) ? 8 : 4),
|
||||
0,
|
||||
false));
|
||||
updateCounter.set(updateCounter.get() + 1);
|
||||
}
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Setter
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
@ -198,7 +211,7 @@ public class PriceFeedService {
|
|||
if (cache.containsKey(currencyCode)) {
|
||||
try {
|
||||
MarketPrice marketPrice = cache.get(currencyCode);
|
||||
if (marketPrice.isValid())
|
||||
if (marketPrice.isRecentExternalPriceAvailable())
|
||||
priceConsumer.accept(marketPrice.getPrice());
|
||||
} catch (Throwable t) {
|
||||
log.warn("Error at applyPriceToConsumer " + t.getMessage());
|
||||
|
@ -243,7 +256,7 @@ public class PriceFeedService {
|
|||
convertedPrice = value.getPrice() / baseCurrencyPrice.getPrice();
|
||||
else
|
||||
convertedPrice = value.getPrice() * baseCurrencyPrice.getPrice();
|
||||
convertedPriceMap.put(e.getKey(), new MarketPrice(value.getCurrencyCode(), convertedPrice, value.getTimestampSec()));
|
||||
convertedPriceMap.put(e.getKey(), new MarketPrice(value.getCurrencyCode(), convertedPrice, value.getTimestampSec(), true));
|
||||
});
|
||||
cache.putAll(convertedPriceMap);
|
||||
break;
|
||||
|
|
|
@ -58,7 +58,7 @@ public class PriceProvider extends HttpClientProvider {
|
|||
final double price = (double) treeMap.get("price");
|
||||
// json uses double for our timestampSec long value...
|
||||
final long timestampSec = MathUtils.doubleToLong((double) treeMap.get("timestampSec"));
|
||||
marketPriceMap.put(currencyCode, new MarketPrice(currencyCode, price, timestampSec));
|
||||
marketPriceMap.put(currencyCode, new MarketPrice(currencyCode, price, timestampSec, true));
|
||||
} catch (Throwable t) {
|
||||
log.error(t.toString());
|
||||
t.printStackTrace();
|
||||
|
|
|
@ -13,6 +13,7 @@ import io.bisq.common.storage.Storage;
|
|||
import io.bisq.common.util.Utilities;
|
||||
import io.bisq.core.app.AppOptionKeys;
|
||||
import io.bisq.core.app.BisqEnvironment;
|
||||
import io.bisq.core.provider.price.PriceFeedService;
|
||||
import io.bisq.network.p2p.P2PService;
|
||||
import io.bisq.network.p2p.storage.HashMapChangedListener;
|
||||
import io.bisq.network.p2p.storage.payload.ProtectedStorageEntry;
|
||||
|
@ -32,6 +33,7 @@ public class TradeStatisticsManager implements PersistedDataHost {
|
|||
private final Storage<TradeStatisticsList> statisticsStorage;
|
||||
private final JsonFileManager jsonFileManager;
|
||||
private final P2PService p2PService;
|
||||
private final PriceFeedService priceFeedService;
|
||||
private final boolean dumpStatistics;
|
||||
private final ObservableSet<TradeStatistics> observableTradeStatisticsSet = FXCollections.observableSet();
|
||||
private final HashSet<TradeStatistics> tradeStatisticsSet = new HashSet<>();
|
||||
|
@ -40,10 +42,12 @@ public class TradeStatisticsManager implements PersistedDataHost {
|
|||
@Inject
|
||||
public TradeStatisticsManager(Storage<TradeStatisticsList> statisticsStorage,
|
||||
P2PService p2PService,
|
||||
PriceFeedService priceFeedService,
|
||||
@Named(Storage.STORAGE_DIR) File storageDir,
|
||||
@Named(AppOptionKeys.DUMP_STATISTICS) boolean dumpStatistics) {
|
||||
this.statisticsStorage = statisticsStorage;
|
||||
this.p2PService = p2PService;
|
||||
this.priceFeedService = priceFeedService;
|
||||
this.dumpStatistics = dumpStatistics;
|
||||
jsonFileManager = new JsonFileManager(storageDir);
|
||||
|
||||
|
@ -129,6 +133,8 @@ public class TradeStatisticsManager implements PersistedDataHost {
|
|||
|
||||
});
|
||||
|
||||
applyBisqMarketPrice();
|
||||
|
||||
statisticsStorage.queueUpForSave(new TradeStatisticsList(new ArrayList<>(tradeStatisticsSet)), 2000);
|
||||
dump();
|
||||
|
||||
|
@ -137,6 +143,16 @@ public class TradeStatisticsManager implements PersistedDataHost {
|
|||
|
||||
}
|
||||
|
||||
private void applyBisqMarketPrice() {
|
||||
List<TradeStatistics> sortedList = new ArrayList<>(tradeStatisticsSet);
|
||||
// sort by date so we have most recent as last entry which we use for displaying the latest price
|
||||
sortedList.sort((o1, o2) -> o1.getTradeDate().compareTo(o2.getTradeDate()));
|
||||
if (!sortedList.isEmpty()) {
|
||||
TradeStatistics tradeStatistics = sortedList.get(sortedList.size() - 1);
|
||||
priceFeedService.setBisqMarketPrice(tradeStatistics.getCurrencyCode(), tradeStatistics.getTradePrice());
|
||||
}
|
||||
}
|
||||
|
||||
public void add(TradeStatistics tradeStatistics, boolean storeLocally) {
|
||||
if (!tradeStatisticsSet.contains(tradeStatistics)) {
|
||||
boolean itemAlreadyAdded = tradeStatisticsSet.stream().filter(e -> (e.getOfferId().equals(tradeStatistics.getOfferId()))).findAny().isPresent();
|
||||
|
@ -144,7 +160,11 @@ public class TradeStatisticsManager implements PersistedDataHost {
|
|||
tradeStatisticsSet.add(tradeStatistics);
|
||||
observableTradeStatisticsSet.add(tradeStatistics);
|
||||
|
||||
tradeStatistics.getTradePrice().getValue();
|
||||
|
||||
if (storeLocally) {
|
||||
applyBisqMarketPrice();
|
||||
|
||||
statisticsStorage.queueUpForSave(new TradeStatisticsList(new ArrayList<>(tradeStatisticsSet)), 2000);
|
||||
dump();
|
||||
}
|
||||
|
|
|
@ -159,7 +159,7 @@ public class MainView extends InitializableView<StackPane, MainViewModel> {
|
|||
});
|
||||
}, 1);
|
||||
}
|
||||
|
||||
|
||||
HBox leftNavPane = new HBox(marketButton, buyButton, sellButton, portfolioButtonHolder, fundsButton, disputesButtonHolder) {{
|
||||
setLeftAnchor(this, 10d);
|
||||
setTopAnchor(this, 0d);
|
||||
|
@ -330,7 +330,7 @@ public class MainView extends InitializableView<StackPane, MainViewModel> {
|
|||
HBox.setMargin(btcAverageIconButton, new Insets(0, 5, 0, 0));
|
||||
btcAverageIconButton.setOnAction(e -> GUIUtil.openWebPage("https://bitcoinaverage.com"));
|
||||
btcAverageIconButton.setVisible(model.isFiatCurrencyPriceFeedSelected.get());
|
||||
btcAverageIconButton.setManaged(model.isFiatCurrencyPriceFeedSelected.get());
|
||||
btcAverageIconButton.setManaged(btcAverageIconButton.isVisible());
|
||||
btcAverageIconButton.visibleProperty().bind(model.isFiatCurrencyPriceFeedSelected);
|
||||
btcAverageIconButton.managedProperty().bind(model.isFiatCurrencyPriceFeedSelected);
|
||||
btcAverageIconButton.setOnMouseEntered(e -> {
|
||||
|
@ -354,7 +354,7 @@ public class MainView extends InitializableView<StackPane, MainViewModel> {
|
|||
HBox.setMargin(poloniexIconButton, new Insets(2, 3, 0, 0));
|
||||
poloniexIconButton.setOnAction(e -> GUIUtil.openWebPage("https://poloniex.com"));
|
||||
poloniexIconButton.setVisible(model.isCryptoCurrencyPriceFeedSelected.get());
|
||||
poloniexIconButton.setManaged(model.isCryptoCurrencyPriceFeedSelected.get());
|
||||
poloniexIconButton.setManaged(poloniexIconButton.isVisible());
|
||||
poloniexIconButton.visibleProperty().bind(model.isCryptoCurrencyPriceFeedSelected);
|
||||
poloniexIconButton.managedProperty().bind(model.isCryptoCurrencyPriceFeedSelected);
|
||||
poloniexIconButton.setOnMouseEntered(e -> {
|
||||
|
@ -372,10 +372,28 @@ public class MainView extends InitializableView<StackPane, MainViewModel> {
|
|||
Label label = new Label(Res.get("mainView.marketPrice.provider"));
|
||||
label.setId("nav-balance-label");
|
||||
label.setPadding(new Insets(0, 5, 0, 2));
|
||||
label.visibleProperty().bind(createBooleanBinding(() -> model.isCryptoCurrencyPriceFeedSelected.get() || model.isFiatCurrencyPriceFeedSelected.get(),
|
||||
model.isCryptoCurrencyPriceFeedSelected, model.isFiatCurrencyPriceFeedSelected));
|
||||
label.visibleProperty().bind(createBooleanBinding(() -> model.isPriceAvailable.get() &&
|
||||
(model.isCryptoCurrencyPriceFeedSelected.get() ||
|
||||
model.isFiatCurrencyPriceFeedSelected.get() ||
|
||||
!model.isExternallyProvidedPrice.get()),
|
||||
model.isPriceAvailable,
|
||||
model.isCryptoCurrencyPriceFeedSelected,
|
||||
model.isFiatCurrencyPriceFeedSelected,
|
||||
model.isExternallyProvidedPrice));
|
||||
label.managedProperty().bind(label.visibleProperty());
|
||||
|
||||
model.isExternallyProvidedPrice.addListener((observable, oldValue, newValue) -> {
|
||||
if (newValue) {
|
||||
label.setText(Res.get("mainView.marketPrice.provider"));
|
||||
label.setTooltip(null);
|
||||
} else {
|
||||
label.setText(Res.get("mainView.marketPrice.bisqInternalPrice"));
|
||||
final Tooltip tooltip = new Tooltip(Res.get("mainView.marketPrice.tooltip.bisqInternalPrice"));
|
||||
tooltip.setStyle("-fx-font-size: 12");
|
||||
label.setTooltip(tooltip);
|
||||
}
|
||||
});
|
||||
|
||||
HBox hBox2 = new HBox();
|
||||
hBox2.getChildren().setAll(label, btcAverageIconButton, poloniexIconButton);
|
||||
|
||||
|
|
|
@ -148,6 +148,8 @@ public class MainViewModel implements ViewModel {
|
|||
final ObjectProperty<PriceFeedComboBoxItem> selectedPriceFeedComboBoxItemProperty = new SimpleObjectProperty<>();
|
||||
final BooleanProperty isFiatCurrencyPriceFeedSelected = new SimpleBooleanProperty(true);
|
||||
final BooleanProperty isCryptoCurrencyPriceFeedSelected = new SimpleBooleanProperty(false);
|
||||
final BooleanProperty isExternallyProvidedPrice = new SimpleBooleanProperty(true);
|
||||
final BooleanProperty isPriceAvailable = new SimpleBooleanProperty(false);
|
||||
final StringProperty availableBalance = new SimpleStringProperty();
|
||||
final StringProperty reservedBalance = new SimpleStringProperty();
|
||||
final StringProperty lockedBalance = new SimpleStringProperty();
|
||||
|
@ -1026,12 +1028,13 @@ public class MainViewModel implements ViewModel {
|
|||
String currencyCode = item.currencyCode;
|
||||
MarketPrice marketPrice = priceFeedService.getMarketPrice(currencyCode);
|
||||
String priceString;
|
||||
if (marketPrice != null && marketPrice.isValid()) {
|
||||
if (marketPrice != null && marketPrice.isPriceAvailable()) {
|
||||
priceString = formatter.formatMarketPrice(marketPrice.getPrice(), currencyCode);
|
||||
item.setIsPriceAvailable(true);
|
||||
item.setPriceAvailable(true);
|
||||
item.setExternallyProvidedPrice(marketPrice.isExternallyProvidedPrice());
|
||||
} else {
|
||||
priceString = Res.get("shared.na");
|
||||
item.setIsPriceAvailable(false);
|
||||
item.setPriceAvailable(false);
|
||||
}
|
||||
item.setDisplayString(formatter.getCurrencyPair(currencyCode) + ": " + priceString);
|
||||
});
|
||||
|
@ -1057,8 +1060,10 @@ public class MainViewModel implements ViewModel {
|
|||
UserThread.runAfter(() -> {
|
||||
if (item != null) {
|
||||
String code = item.currencyCode;
|
||||
isFiatCurrencyPriceFeedSelected.set(CurrencyUtil.isFiatCurrency(code) && CurrencyUtil.getFiatCurrency(code).isPresent() && item.isPriceAvailable());
|
||||
isCryptoCurrencyPriceFeedSelected.set(CurrencyUtil.isCryptoCurrency(code) && CurrencyUtil.getCryptoCurrency(code).isPresent() && item.isPriceAvailable());
|
||||
isFiatCurrencyPriceFeedSelected.set(CurrencyUtil.isFiatCurrency(code) && CurrencyUtil.getFiatCurrency(code).isPresent() && item.isPriceAvailable() && item.isExternallyProvidedPrice());
|
||||
isCryptoCurrencyPriceFeedSelected.set(CurrencyUtil.isCryptoCurrency(code) && CurrencyUtil.getCryptoCurrency(code).isPresent() && item.isPriceAvailable() && item.isExternallyProvidedPrice());
|
||||
isExternallyProvidedPrice.set(item.isExternallyProvidedPrice());
|
||||
isPriceAvailable.set(item.isPriceAvailable());
|
||||
}
|
||||
}, 100, TimeUnit.MILLISECONDS);
|
||||
}
|
||||
|
|
|
@ -2,15 +2,18 @@ package io.bisq.gui.main;
|
|||
|
||||
import javafx.beans.property.SimpleStringProperty;
|
||||
import javafx.beans.property.StringProperty;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
public class PriceFeedComboBoxItem {
|
||||
private static final Logger log = LoggerFactory.getLogger(PriceFeedComboBoxItem.class);
|
||||
|
||||
public final String currencyCode;
|
||||
public final StringProperty displayStringProperty = new SimpleStringProperty();
|
||||
@Setter
|
||||
@Getter
|
||||
private boolean isPriceAvailable;
|
||||
@Setter
|
||||
@Getter
|
||||
private boolean isExternallyProvidedPrice;
|
||||
|
||||
public PriceFeedComboBoxItem(String currencyCode) {
|
||||
this.currencyCode = currencyCode;
|
||||
|
@ -19,12 +22,4 @@ public class PriceFeedComboBoxItem {
|
|||
public void setDisplayString(String displayString) {
|
||||
this.displayStringProperty.set(displayString);
|
||||
}
|
||||
|
||||
public void setIsPriceAvailable(boolean isPriceAvailable) {
|
||||
this.isPriceAvailable = isPriceAvailable;
|
||||
}
|
||||
|
||||
public boolean isPriceAvailable() {
|
||||
return isPriceAvailable;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -146,7 +146,7 @@ class SpreadViewModel extends ActivatableViewModel {
|
|||
// TODO maybe show extra colums with spread and use real amount diff
|
||||
// not % based. e.g. diff between best buy and sell offer (of small amounts its a smaller gain)
|
||||
|
||||
if (spread != null && marketPrice != null && marketPrice.isValid()) {
|
||||
if (spread != null && marketPrice != null && marketPrice.isPriceAvailable()) {
|
||||
double marketPriceAsDouble = marketPrice.getPrice();
|
||||
final double precision = isFiatCurrency ?
|
||||
Math.pow(10, Fiat.SMALLEST_UNIT_EXPONENT) :
|
||||
|
|
|
@ -301,7 +301,7 @@ class CreateOfferViewModel extends ActivatableWithDataModel<CreateOfferDataModel
|
|||
dataModel.calculateTotalToPay();
|
||||
|
||||
if (!inputIsMarketBasedPrice) {
|
||||
if (marketPrice != null && marketPrice.isValid()) {
|
||||
if (marketPrice != null && marketPrice.isRecentExternalPriceAvailable()) {
|
||||
double marketPriceAsDouble = marketPrice.getPrice();
|
||||
try {
|
||||
double priceAsDouble = btcFormatter.parseNumberStringToDouble(price.get());
|
||||
|
@ -338,7 +338,7 @@ class CreateOfferViewModel extends ActivatableWithDataModel<CreateOfferDataModel
|
|||
} else {
|
||||
final String currencyCode = dataModel.getTradeCurrencyCode().get();
|
||||
MarketPrice marketPrice = priceFeedService.getMarketPrice(currencyCode);
|
||||
if (marketPrice != null && marketPrice.isValid()) {
|
||||
if (marketPrice != null && marketPrice.isRecentExternalPriceAvailable()) {
|
||||
percentage = MathUtils.roundDouble(percentage, 4);
|
||||
double marketPriceAsDouble = marketPrice.getPrice();
|
||||
final boolean isCryptoCurrency = CurrencyUtil.isCryptoCurrency(currencyCode);
|
||||
|
@ -462,8 +462,8 @@ class CreateOfferViewModel extends ActivatableWithDataModel<CreateOfferDataModel
|
|||
|
||||
private void updateMarketPriceAvailable() {
|
||||
marketPrice = priceFeedService.getMarketPrice(dataModel.getTradeCurrencyCode().get());
|
||||
marketPriceAvailableProperty.set(marketPrice == null ? 0 : 1);
|
||||
dataModel.setMarketPriceAvailable(marketPrice != null);
|
||||
marketPriceAvailableProperty.set(marketPrice == null || !marketPrice.isExternallyProvidedPrice() ? 0 : 1);
|
||||
dataModel.setMarketPriceAvailable(marketPrice != null && marketPrice.isExternallyProvidedPrice());
|
||||
}
|
||||
|
||||
private void addListeners() {
|
||||
|
@ -603,7 +603,7 @@ class CreateOfferViewModel extends ActivatableWithDataModel<CreateOfferDataModel
|
|||
dataModel.onCurrencySelected(tradeCurrency);
|
||||
|
||||
marketPrice = priceFeedService.getMarketPrice(dataModel.getTradeCurrencyCode().get());
|
||||
marketPriceAvailableProperty.set(marketPrice == null ? 0 : 1);
|
||||
marketPriceAvailableProperty.set(marketPrice == null || !marketPrice.isExternallyProvidedPrice() ? 0 : 1);
|
||||
updateButtonDisableState();
|
||||
}
|
||||
|
||||
|
|
|
@ -45,7 +45,7 @@ public class BsqValidator extends AltcoinValidator {
|
|||
@Inject
|
||||
public BsqValidator(BsqFormatter bsqFormatter) {
|
||||
this.bsqFormatter = bsqFormatter;
|
||||
setMaxValue(bsqFormatter.parseToCoin("2300000")); // TODO make it lower
|
||||
//setMaxValue(bsqFormatter.parseToCoin("2500000"));
|
||||
}
|
||||
|
||||
public void setMaxValue(@NotNull Coin maxValue) {
|
||||
|
|
Loading…
Add table
Reference in a new issue