diff --git a/core/src/main/java/bisq/core/app/AppOptionKeys.java b/core/src/main/java/bisq/core/app/AppOptionKeys.java index 6c44f7d4f81..c9036c38865 100644 --- a/core/src/main/java/bisq/core/app/AppOptionKeys.java +++ b/core/src/main/java/bisq/core/app/AppOptionKeys.java @@ -29,4 +29,6 @@ public class AppOptionKeys { public static final String IGNORE_DEV_MSG_KEY = "ignoreDevMsg"; public static final String USE_DEV_PRIVILEGE_KEYS = "useDevPrivilegeKeys"; public static final String REFERRAL_ID = "referralId"; + public static final String HTTP_API_HOST = "httpApiHost"; + public static final String HTTP_API_PORT = "httpApiPort"; } diff --git a/core/src/main/java/bisq/core/app/BisqEnvironment.java b/core/src/main/java/bisq/core/app/BisqEnvironment.java index 5e463e76062..04883943566 100644 --- a/core/src/main/java/bisq/core/app/BisqEnvironment.java +++ b/core/src/main/java/bisq/core/app/BisqEnvironment.java @@ -193,8 +193,10 @@ private static String appDataDir(String userDataDir, String appName) { protected final String btcNodes, seedNodes, ignoreDevMsg, useDevPrivilegeKeys, useDevMode, useTorForBtc, rpcUser, rpcPassword, rpcPort, rpcBlockNotificationPort, dumpBlockchainData, fullDaoNode, myAddress, banList, dumpStatistics, maxMemory, socks5ProxyBtcAddress, - socks5ProxyHttpAddress, useAllProvidedNodes, numConnectionForBtc, genesisTxId, genesisBlockHeight, referralId, daoActivated; - + socks5ProxyHttpAddress, useAllProvidedNodes, numConnectionForBtc, genesisTxId, genesisBlockHeight, + referralId, daoActivated; + @Getter + protected final String httpApiHost, httpApiPort; public BisqEnvironment(OptionSet options) { this(new JOptCommandLinePropertySource(BISQ_COMMANDLINE_PROPERTY_SOURCE_NAME, checkNotNull( @@ -237,6 +239,12 @@ public BisqEnvironment(PropertySource commandLineProperties) { referralId = commandLineProperties.containsProperty(AppOptionKeys.REFERRAL_ID) ? (String) commandLineProperties.getProperty(AppOptionKeys.REFERRAL_ID) : ""; + httpApiHost = commandLineProperties.containsProperty(AppOptionKeys.HTTP_API_HOST) ? + (String) commandLineProperties.getProperty(AppOptionKeys.HTTP_API_HOST) : + "127.0.0.1"; + httpApiPort = commandLineProperties.containsProperty(AppOptionKeys.HTTP_API_PORT) ? + (String) commandLineProperties.getProperty(AppOptionKeys.HTTP_API_PORT) : + "8080"; useDevMode = commandLineProperties.containsProperty(CommonOptionKeys.USE_DEV_MODE) ? (String) commandLineProperties.getProperty(CommonOptionKeys.USE_DEV_MODE) : ""; diff --git a/core/src/main/java/bisq/core/app/BisqExecutable.java b/core/src/main/java/bisq/core/app/BisqExecutable.java index cb99f061f5f..addecbcf82c 100644 --- a/core/src/main/java/bisq/core/app/BisqExecutable.java +++ b/core/src/main/java/bisq/core/app/BisqExecutable.java @@ -73,7 +73,7 @@ import static java.lang.String.join; @Slf4j -public abstract class BisqExecutable implements GracefulShutDownHandler { +public abstract class BisqExecutable implements GracefulShutDownHandler, BisqSetup.BisqSetupCompleteListener { static { Utilities.removeCryptographyRestrictions(); } @@ -239,9 +239,12 @@ protected void onApplicationStarted() { protected void startAppSetup() { BisqSetup bisqSetup = injector.getInstance(BisqSetup.class); + bisqSetup.addBisqSetupCompleteListener(this); bisqSetup.start(); } + public abstract void onSetupComplete(); + /////////////////////////////////////////////////////////////////////////////////////////// // GracefulShutDownHandler implementation @@ -360,6 +363,12 @@ protected void customizeOptionParsing(OptionParser parser) { parser.accepts(AppOptionKeys.REFERRAL_ID, description("Optional Referral ID (e.g. for API users or pro market makers)", "")) .withRequiredArg(); + parser.accepts(AppOptionKeys.HTTP_API_HOST, + description("Optional HTTP API host", "127.0.0.1")) + .withRequiredArg(); + parser.accepts(AppOptionKeys.HTTP_API_PORT, + description("Optional HTTP API port", "8080")) + .withRequiredArg(); parser.accepts(CommonOptionKeys.USE_DEV_MODE, description("Enables dev mode which is used for convenience for developer testing", false)) .withRequiredArg() diff --git a/core/src/main/java/bisq/core/app/BisqFacade.java b/core/src/main/java/bisq/core/app/BisqFacade.java index a496e0bab9a..4a7b6997112 100644 --- a/core/src/main/java/bisq/core/app/BisqFacade.java +++ b/core/src/main/java/bisq/core/app/BisqFacade.java @@ -17,7 +17,7 @@ package bisq.core.app; -import bisq.core.btc.BalanceModel; +import bisq.core.btc.Balances; import bisq.core.presentation.BalancePresentation; import bisq.common.app.Version; @@ -29,12 +29,12 @@ * E.g. useful for different APIs to access data of different domains of Bisq. */ public class BisqFacade { - private final BalanceModel balanceModel; + private final Balances balances; private final BalancePresentation balancePresentation; @Inject - public BisqFacade(BalanceModel balanceModel, BalancePresentation balancePresentation) { - this.balanceModel = balanceModel; + public BisqFacade(Balances balances, BalancePresentation balancePresentation) { + this.balances = balances; this.balancePresentation = balancePresentation; } @@ -43,8 +43,7 @@ public String getVersion() { } public long getAvailableBalance() { - balanceModel.updateBalance(); - return balanceModel.getAvailableBalance().get().getValue(); + return balances.getAvailableBalance().get().getValue(); } public String getAvailableBalanceAsString() { diff --git a/core/src/main/java/bisq/core/app/BisqHeadlessApp.java b/core/src/main/java/bisq/core/app/BisqHeadlessApp.java index bd0fcc8e77a..fb76cba4367 100644 --- a/core/src/main/java/bisq/core/app/BisqHeadlessApp.java +++ b/core/src/main/java/bisq/core/app/BisqHeadlessApp.java @@ -54,7 +54,6 @@ public BisqHeadlessApp() { public void startApplication() { try { bisqSetup = injector.getInstance(BisqSetup.class); - bisqSetup.addBisqSetupCompleteListener(this); corruptedDatabaseFilesHandler = injector.getInstance(CorruptedDatabaseFilesHandler.class); tradeManager = injector.getInstance(TradeManager.class); diff --git a/core/src/main/java/bisq/core/app/BisqHeadlessAppMain.java b/core/src/main/java/bisq/core/app/BisqHeadlessAppMain.java index 22a9543f81f..16b6acb1ab5 100644 --- a/core/src/main/java/bisq/core/app/BisqHeadlessAppMain.java +++ b/core/src/main/java/bisq/core/app/BisqHeadlessAppMain.java @@ -80,6 +80,11 @@ protected void onApplicationLaunched() { headlessApp.setGracefulShutDownHandler(this); } + @Override + public void onSetupComplete() { + log.info("onSetupComplete"); + } + /////////////////////////////////////////////////////////////////////////////////////////// // We continue with a series of synchronous execution tasks /////////////////////////////////////////////////////////////////////////////////////////// diff --git a/core/src/main/java/bisq/core/app/BisqSetup.java b/core/src/main/java/bisq/core/app/BisqSetup.java index 12dd7750788..fc7af9dabca 100644 --- a/core/src/main/java/bisq/core/app/BisqSetup.java +++ b/core/src/main/java/bisq/core/app/BisqSetup.java @@ -24,8 +24,7 @@ import bisq.core.arbitration.ArbitratorManager; import bisq.core.arbitration.DisputeManager; import bisq.core.btc.AddressEntry; -import bisq.core.btc.BalanceModel; -import bisq.core.btc.listeners.BalanceListener; +import bisq.core.btc.Balances; import bisq.core.btc.wallet.BtcWalletService; import bisq.core.btc.wallet.WalletsManager; import bisq.core.btc.wallet.WalletsSetup; @@ -38,14 +37,12 @@ import bisq.core.notifications.alerts.TradeEvents; import bisq.core.notifications.alerts.market.MarketAlerts; import bisq.core.notifications.alerts.price.PriceAlert; -import bisq.core.offer.OpenOffer; import bisq.core.offer.OpenOfferManager; import bisq.core.payment.AccountAgeWitnessService; import bisq.core.payment.PaymentAccount; import bisq.core.payment.payload.PaymentMethod; import bisq.core.provider.fee.FeeService; import bisq.core.provider.price.PriceFeedService; -import bisq.core.trade.Trade; import bisq.core.trade.TradeManager; import bisq.core.trade.statistics.TradeStatisticsManager; import bisq.core.user.Preferences; @@ -68,7 +65,6 @@ import bisq.common.util.Utilities; import org.bitcoinj.core.Coin; -import org.bitcoinj.core.Transaction; import javax.inject.Inject; @@ -83,7 +79,6 @@ import javafx.beans.property.StringProperty; import javafx.beans.value.ChangeListener; -import javafx.collections.ListChangeListener; import javafx.collections.SetChangeListener; import org.spongycastle.crypto.params.KeyParameter; @@ -120,7 +115,7 @@ public interface BisqSetupCompleteListener { private final WalletsManager walletsManager; private final WalletsSetup walletsSetup; private final BtcWalletService btcWalletService; - private final BalanceModel balanceModel; + private final Balances balances; private final PriceFeedService priceFeedService; private final ArbitratorManager arbitratorManager; private final P2PService p2PService; @@ -189,7 +184,7 @@ public BisqSetup(P2PNetworkSetup p2PNetworkSetup, WalletsManager walletsManager, WalletsSetup walletsSetup, BtcWalletService btcWalletService, - BalanceModel balanceModel, + Balances balances, PriceFeedService priceFeedService, ArbitratorManager arbitratorManager, P2PService p2PService, @@ -224,7 +219,7 @@ public BisqSetup(P2PNetworkSetup p2PNetworkSetup, this.walletsManager = walletsManager; this.walletsSetup = walletsSetup; this.btcWalletService = btcWalletService; - this.balanceModel = balanceModel; + this.balances = balances; this.priceFeedService = priceFeedService; this.arbitratorManager = arbitratorManager; this.p2PService = p2PService; @@ -568,29 +563,12 @@ private void initDomainServices() { disputeManager.onAllServicesInitialized(); tradeManager.onAllServicesInitialized(); - tradeManager.getTradableList().addListener((ListChangeListener) change -> balanceModel.updateBalance()); - tradeManager.getAddressEntriesForAvailableBalanceStream() - .filter(addressEntry -> addressEntry.getOfferId() != null) - .forEach(addressEntry -> { - log.debug("swapPendingOfferFundingEntries, offerId={}, OFFER_FUNDING", addressEntry.getOfferId()); - btcWalletService.swapTradeEntryToAvailableEntry(addressEntry.getOfferId(), AddressEntry.Context.OFFER_FUNDING); - }); - - btcWalletService.addBalanceListener(new BalanceListener() { - @Override - public void onBalanceChanged(Coin balance, Transaction tx) { - balanceModel.updateBalance(); - } - }); if (walletsSetup.downloadPercentageProperty().get() == 1) checkForLockedUpFunds(); - balanceModel.updateBalance(); - - openOfferManager.getObservableList().addListener((ListChangeListener) c -> balanceModel.updateBalance()); openOfferManager.onAllServicesInitialized(); - + balances.onAllServicesInitialized(); arbitratorManager.onAllServicesInitialized(); alertManager.alertMessageProperty().addListener((observable, oldValue, newValue) -> diff --git a/core/src/main/java/bisq/core/app/misc/ExecutableForAppWithP2p.java b/core/src/main/java/bisq/core/app/misc/ExecutableForAppWithP2p.java index 0d8c35395c3..5e721ebff94 100644 --- a/core/src/main/java/bisq/core/app/misc/ExecutableForAppWithP2p.java +++ b/core/src/main/java/bisq/core/app/misc/ExecutableForAppWithP2p.java @@ -60,6 +60,11 @@ protected void configUserThread() { UserThread.setExecutor(Executors.newSingleThreadExecutor(threadFactory)); } + @Override + public void onSetupComplete() { + log.info("onSetupComplete"); + } + // We don't use the gracefulShutDown implementation of the super class as we have a limited set of modules @Override public void gracefulShutDown(ResultHandler resultHandler) { diff --git a/core/src/main/java/bisq/core/btc/BalanceModel.java b/core/src/main/java/bisq/core/btc/BalanceModel.java deleted file mode 100644 index 87b1af4a6c1..00000000000 --- a/core/src/main/java/bisq/core/btc/BalanceModel.java +++ /dev/null @@ -1,116 +0,0 @@ -/* - * This file is part of Bisq. - * - * Bisq 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. - * - * Bisq 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 Bisq. If not, see . - */ - -package bisq.core.btc; - -import bisq.core.btc.wallet.BtcWalletService; -import bisq.core.offer.OpenOfferManager; -import bisq.core.trade.Trade; -import bisq.core.trade.TradeManager; -import bisq.core.trade.closed.ClosedTradableManager; -import bisq.core.trade.failed.FailedTradesManager; - -import org.bitcoinj.core.Address; -import org.bitcoinj.core.Coin; - -import javax.inject.Inject; - -import javafx.beans.property.ObjectProperty; -import javafx.beans.property.SimpleObjectProperty; - -import java.util.Objects; -import java.util.Optional; -import java.util.stream.Stream; - -import lombok.Getter; - -public class BalanceModel { - private final TradeManager tradeManager; - private final BtcWalletService btcWalletService; - private final OpenOfferManager openOfferManager; - private final ClosedTradableManager closedTradableManager; - private final FailedTradesManager failedTradesManager; - - @Getter - private final ObjectProperty availableBalance = new SimpleObjectProperty<>(); - @Getter - private final ObjectProperty reservedBalance = new SimpleObjectProperty<>(); - @Getter - private final ObjectProperty lockedBalance = new SimpleObjectProperty<>(); - - @Inject - public BalanceModel(TradeManager tradeManager, BtcWalletService btcWalletService, OpenOfferManager openOfferManager, - ClosedTradableManager closedTradableManager, FailedTradesManager failedTradesManager) { - this.tradeManager = tradeManager; - this.btcWalletService = btcWalletService; - this.openOfferManager = openOfferManager; - this.closedTradableManager = closedTradableManager; - this.failedTradesManager = failedTradesManager; - } - - public void updateBalance() { - //TODO check if still needed - /* // Without delaying to the next cycle it does not update. - // Seems order of events we are listening on causes that... - UserThread.execute(() -> { - updateAvailableBalance(); - updateReservedBalance(); - updateLockedBalance(); - });*/ - updateAvailableBalance(); - updateReservedBalance(); - updateLockedBalance(); - // TODO add lockingBalance - } - - private void updateAvailableBalance() { - Coin totalAvailableBalance = Coin.valueOf(tradeManager.getAddressEntriesForAvailableBalanceStream() - .mapToLong(addressEntry -> btcWalletService.getBalanceForAddress(addressEntry.getAddress()).getValue()) - .sum()); - availableBalance.set(totalAvailableBalance); - } - - private void updateReservedBalance() { - Coin sum = Coin.valueOf(openOfferManager.getObservableList().stream() - .map(openOffer -> { - final Optional addressEntryOptional = btcWalletService.getAddressEntry(openOffer.getId(), AddressEntry.Context.RESERVED_FOR_TRADE); - if (addressEntryOptional.isPresent()) { - Address address = addressEntryOptional.get().getAddress(); - return btcWalletService.getBalanceForAddress(address); - } else { - return null; - } - }) - .filter(Objects::nonNull) - .mapToLong(Coin::getValue) - .sum()); - - reservedBalance.set(sum); - } - - private void updateLockedBalance() { - Stream lockedTrades = Stream.concat(closedTradableManager.getLockedTradesStream(), failedTradesManager.getLockedTradesStream()); - lockedTrades = Stream.concat(lockedTrades, tradeManager.getLockedTradesStream()); - Coin sum = Coin.valueOf(lockedTrades - .mapToLong(trade -> { - final Optional addressEntryOptional = btcWalletService.getAddressEntry(trade.getId(), AddressEntry.Context.MULTI_SIG); - return addressEntryOptional.map(addressEntry -> addressEntry.getCoinLockedInMultiSig().getValue()).orElse(0L); - }) - .sum()); - lockedBalance.set(sum); - } -} diff --git a/core/src/main/java/bisq/core/btc/BalanceUtil.java b/core/src/main/java/bisq/core/btc/BalanceUtil.java new file mode 100644 index 00000000000..bdad2153bdc --- /dev/null +++ b/core/src/main/java/bisq/core/btc/BalanceUtil.java @@ -0,0 +1,81 @@ +/* + * This file is part of Bisq. + * + * Bisq 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. + * + * Bisq 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 Bisq. If not, see . + */ + +package bisq.core.btc; + +import bisq.core.btc.wallet.BtcWalletService; +import bisq.core.offer.OpenOffer; +import bisq.core.offer.OpenOfferManager; +import bisq.core.trade.Trade; +import bisq.core.trade.TradeManager; +import bisq.core.trade.closed.ClosedTradableManager; +import bisq.core.trade.failed.FailedTradesManager; + +import bisq.common.util.Tuple2; + +import javax.inject.Inject; + +import java.util.Objects; +import java.util.stream.Stream; + +public class BalanceUtil { + private final TradeManager tradeManager; + private final BtcWalletService btcWalletService; + private final OpenOfferManager openOfferManager; + private final ClosedTradableManager closedTradableManager; + private final FailedTradesManager failedTradesManager; + + @Inject + public BalanceUtil(TradeManager tradeManager, BtcWalletService btcWalletService, OpenOfferManager openOfferManager, + ClosedTradableManager closedTradableManager, FailedTradesManager failedTradesManager) { + this.tradeManager = tradeManager; + this.btcWalletService = btcWalletService; + this.openOfferManager = openOfferManager; + this.closedTradableManager = closedTradableManager; + this.failedTradesManager = failedTradesManager; + } + + public Stream getAddressEntriesForAvailableFunds() { + return tradeManager.getAddressEntriesForAvailableFundsStream(); + } + + public Stream getAddressEntriesForReservedFunds() { + return getOpenOfferAndAddressEntriesForReservedFunds().map(tuple2 -> tuple2.second); + } + + public Stream> getOpenOfferAndAddressEntriesForReservedFunds() { + return openOfferManager.getObservableList().stream() + .map(openOffer -> btcWalletService.getAddressEntry(openOffer.getId(), AddressEntry.Context.RESERVED_FOR_TRADE) + .map(addressEntry -> new Tuple2<>(openOffer, addressEntry)) + .orElse(null)) + .filter(Objects::nonNull); + } + + public Stream getAddressEntriesForLockedFunds() { + return getTradesAndAddressEntriesForLockedFunds().map(tuple2 -> tuple2.second); + } + + public Stream> getTradesAndAddressEntriesForLockedFunds() { + Stream lockedTrades = Stream.concat(closedTradableManager.getLockedTradesStream(), failedTradesManager.getLockedTradesStream()); + lockedTrades = Stream.concat(lockedTrades, tradeManager.getLockedTradesStream()); + return lockedTrades + .map(trade -> btcWalletService.getAddressEntry(trade.getId(), AddressEntry.Context.MULTI_SIG) + .map(addressEntry -> new Tuple2<>(trade, addressEntry)) + .orElse(null)) + .filter(Objects::nonNull); + } +} diff --git a/core/src/main/java/bisq/core/btc/Balances.java b/core/src/main/java/bisq/core/btc/Balances.java new file mode 100644 index 00000000000..87f86f722b6 --- /dev/null +++ b/core/src/main/java/bisq/core/btc/Balances.java @@ -0,0 +1,108 @@ +/* + * This file is part of Bisq. + * + * Bisq 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. + * + * Bisq 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 Bisq. If not, see . + */ + +package bisq.core.btc; + +import bisq.core.btc.listeners.BalanceListener; +import bisq.core.btc.wallet.BtcWalletService; +import bisq.core.offer.OpenOffer; +import bisq.core.offer.OpenOfferManager; +import bisq.core.trade.Trade; +import bisq.core.trade.TradeManager; +import bisq.core.trade.closed.ClosedTradableManager; +import bisq.core.trade.failed.FailedTradesManager; + +import bisq.common.UserThread; + +import org.bitcoinj.core.Coin; +import org.bitcoinj.core.Transaction; + +import javax.inject.Inject; + +import javafx.beans.property.ObjectProperty; +import javafx.beans.property.SimpleObjectProperty; + +import javafx.collections.ListChangeListener; + +import lombok.Getter; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +public class Balances { + private final BalanceUtil balanceUtil; + private final TradeManager tradeManager; + private final BtcWalletService btcWalletService; + private final OpenOfferManager openOfferManager; + + @Getter + private final ObjectProperty availableBalance = new SimpleObjectProperty<>(); + @Getter + private final ObjectProperty reservedBalance = new SimpleObjectProperty<>(); + @Getter + private final ObjectProperty lockedBalance = new SimpleObjectProperty<>(); + + @Inject + public Balances(BalanceUtil balanceUtil, TradeManager tradeManager, BtcWalletService btcWalletService, OpenOfferManager openOfferManager, + ClosedTradableManager closedTradableManager, FailedTradesManager failedTradesManager) { + this.balanceUtil = balanceUtil; + this.tradeManager = tradeManager; + this.btcWalletService = btcWalletService; + this.openOfferManager = openOfferManager; + } + + public void onAllServicesInitialized() { + openOfferManager.getObservableList().addListener((ListChangeListener) c -> updateBalance()); + tradeManager.getTradableList().addListener((ListChangeListener) change -> updateBalance()); + btcWalletService.addBalanceListener(new BalanceListener() { + @Override + public void onBalanceChanged(Coin balance, Transaction tx) { + updateBalance(); + } + }); + updateBalance(); + } + + private void updateBalance() { + // Need to delay a bit to get the balances correct + UserThread.execute(() -> { + updateAvailableBalance(); + updateReservedBalance(); + updateLockedBalance(); + }); + } + + private void updateAvailableBalance() { + Coin sum = Coin.valueOf(balanceUtil.getAddressEntriesForAvailableFunds() + .mapToLong(addressEntry -> btcWalletService.getBalanceForAddress(addressEntry.getAddress()).value) + .sum()); + availableBalance.set(sum); + } + + private void updateReservedBalance() { + Coin sum = Coin.valueOf(balanceUtil.getAddressEntriesForReservedFunds() + .mapToLong(addressEntry -> btcWalletService.getBalanceForAddress(addressEntry.getAddress()).value) + .sum()); + reservedBalance.set(sum); + } + + private void updateLockedBalance() { + Coin sum = Coin.valueOf(balanceUtil.getAddressEntriesForLockedFunds() + .mapToLong(addressEntry -> addressEntry.getCoinLockedInMultiSig().getValue()) + .sum()); + lockedBalance.set(sum); + } +} diff --git a/core/src/main/java/bisq/core/btc/BitcoinModule.java b/core/src/main/java/bisq/core/btc/BitcoinModule.java index 8faf462e1f1..39d3124935b 100644 --- a/core/src/main/java/bisq/core/btc/BitcoinModule.java +++ b/core/src/main/java/bisq/core/btc/BitcoinModule.java @@ -74,7 +74,8 @@ protected void configure() { bind(BsqCoinSelector.class).in(Singleton.class); bind(NonBsqCoinSelector.class).in(Singleton.class); bind(BitcoinNodes.class).in(Singleton.class); - bind(BalanceModel.class).in(Singleton.class); + bind(Balances.class).in(Singleton.class); + bind(BalanceUtil.class).in(Singleton.class); bind(PriceNodeHttpClient.class).in(Singleton.class); diff --git a/core/src/main/java/bisq/core/offer/OfferUtil.java b/core/src/main/java/bisq/core/offer/OfferUtil.java index 3a1dd3a0401..178c1e7ba67 100644 --- a/core/src/main/java/bisq/core/offer/OfferUtil.java +++ b/core/src/main/java/bisq/core/offer/OfferUtil.java @@ -20,23 +20,44 @@ import bisq.core.app.BisqEnvironment; import bisq.core.btc.Restrictions; import bisq.core.btc.wallet.BsqWalletService; +import bisq.core.filter.FilterManager; +import bisq.core.locale.Country; +import bisq.core.locale.CurrencyUtil; +import bisq.core.locale.Res; import bisq.core.monetary.Price; import bisq.core.monetary.Volume; +import bisq.core.payment.AccountAgeWitnessService; +import bisq.core.payment.BankAccount; +import bisq.core.payment.CountryBasedPaymentAccount; +import bisq.core.payment.F2FAccount; +import bisq.core.payment.PaymentAccount; +import bisq.core.payment.SameBankAccount; +import bisq.core.payment.SepaAccount; +import bisq.core.payment.SepaInstantAccount; +import bisq.core.payment.SpecificBanksAccount; import bisq.core.provider.fee.FeeService; +import bisq.core.trade.statistics.ReferralIdService; import bisq.core.user.Preferences; import bisq.core.util.CoinUtil; +import bisq.network.p2p.P2PService; + import bisq.common.util.MathUtils; import org.bitcoinj.core.Coin; import com.google.common.annotations.VisibleForTesting; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Map; + import lombok.extern.slf4j.Slf4j; import javax.annotation.Nullable; import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; /** * This class holds utility methods for the creation of an Offer. @@ -246,4 +267,102 @@ static Coin getAdjustedAmount(Coin amount, Price price, long maxTradeLimit, int adjustedAmount = Math.min(maxTradeLimit, adjustedAmount); return Coin.valueOf(adjustedAmount); } + + public static ArrayList getAcceptedCountryCodes(PaymentAccount paymentAccount) { + ArrayList acceptedCountryCodes = null; + if (paymentAccount instanceof SepaAccount) { + acceptedCountryCodes = new ArrayList<>(((SepaAccount) paymentAccount).getAcceptedCountryCodes()); + } else if (paymentAccount instanceof SepaInstantAccount) { + acceptedCountryCodes = new ArrayList<>(((SepaInstantAccount) paymentAccount).getAcceptedCountryCodes()); + } else if (paymentAccount instanceof CountryBasedPaymentAccount) { + acceptedCountryCodes = new ArrayList<>(); + Country country = ((CountryBasedPaymentAccount) paymentAccount).getCountry(); + if (country != null) + acceptedCountryCodes.add(country.code); + } + return acceptedCountryCodes; + } + + public static ArrayList getAcceptedBanks(PaymentAccount paymentAccount) { + ArrayList acceptedBanks = null; + if (paymentAccount instanceof SpecificBanksAccount) { + acceptedBanks = new ArrayList<>(((SpecificBanksAccount) paymentAccount).getAcceptedBanks()); + } else if (paymentAccount instanceof SameBankAccount) { + acceptedBanks = new ArrayList<>(); + acceptedBanks.add(((SameBankAccount) paymentAccount).getBankId()); + } + return acceptedBanks; + } + + public static String getBankId(PaymentAccount paymentAccount) { + return paymentAccount instanceof BankAccount ? ((BankAccount) paymentAccount).getBankId() : null; + } + + // That is optional and set to null if not supported (AltCoins, OKPay,...) + public static String getCountryCode(PaymentAccount paymentAccount) { + if (paymentAccount instanceof CountryBasedPaymentAccount) { + Country country = ((CountryBasedPaymentAccount) paymentAccount).getCountry(); + return country != null ? country.code : null; + } else { + return null; + } + } + + public static long getMaxTradeLimit(AccountAgeWitnessService accountAgeWitnessService, PaymentAccount paymentAccount, String currencyCode) { + if (paymentAccount != null) + return accountAgeWitnessService.getMyTradeLimit(paymentAccount, currencyCode); + else + return 0; + } + + public static long getMaxTradePeriod(PaymentAccount paymentAccount) { + return paymentAccount.getPaymentMethod().getMaxTradePeriod(); + } + + public static Map getExtraDataMap(AccountAgeWitnessService accountAgeWitnessService, + ReferralIdService referralIdService, + PaymentAccount paymentAccount, + String currencyCode) { + Map extraDataMap = null; + if (CurrencyUtil.isFiatCurrency(currencyCode)) { + extraDataMap = new HashMap<>(); + final String myWitnessHashAsHex = accountAgeWitnessService.getMyWitnessHashAsHex(paymentAccount.getPaymentAccountPayload()); + extraDataMap.put(OfferPayload.ACCOUNT_AGE_WITNESS_HASH, myWitnessHashAsHex); + } + + if (referralIdService.getOptionalReferralId().isPresent()) { + if (extraDataMap == null) + extraDataMap = new HashMap<>(); + extraDataMap.put(OfferPayload.REFERRAL_ID, referralIdService.getOptionalReferralId().get()); + } + + if (paymentAccount instanceof F2FAccount) { + if (extraDataMap == null) + extraDataMap = new HashMap<>(); + extraDataMap.put(OfferPayload.F2F_CITY, ((F2FAccount) paymentAccount).getCity()); + extraDataMap.put(OfferPayload.F2F_EXTRA_INFO, ((F2FAccount) paymentAccount).getExtraInfo()); + } + + return extraDataMap; + } + + public static void validateOfferData(FilterManager filterManager, + P2PService p2PService, + Coin buyerSecurityDepositAsCoin, + PaymentAccount paymentAccount, + String currencyCode, Coin makerFeeAsCoin) { + checkArgument(buyerSecurityDepositAsCoin.compareTo(Restrictions.getMaxBuyerSecurityDeposit()) <= 0, + "securityDeposit must be not exceed " + + Restrictions.getMaxBuyerSecurityDeposit().toFriendlyString()); + checkArgument(buyerSecurityDepositAsCoin.compareTo(Restrictions.getMinBuyerSecurityDeposit()) >= 0, + "securityDeposit must be not be less than " + + Restrictions.getMinBuyerSecurityDeposit().toFriendlyString()); + + checkArgument(!filterManager.isCurrencyBanned(currencyCode), + Res.get("offerbook.warning.currencyBanned")); + checkArgument(!filterManager.isPaymentMethodBanned(paymentAccount.getPaymentMethod()), + Res.get("offerbook.warning.paymentMethodBanned")); + checkNotNull(makerFeeAsCoin, "makerFee must not be null"); + checkNotNull(p2PService.getAddress(), "Address must not be null"); + } } diff --git a/core/src/main/java/bisq/core/offer/TxFeeEstimation.java b/core/src/main/java/bisq/core/offer/TxFeeEstimation.java new file mode 100644 index 00000000000..cd4914f6709 --- /dev/null +++ b/core/src/main/java/bisq/core/offer/TxFeeEstimation.java @@ -0,0 +1,148 @@ +/* + * This file is part of Bisq. + * + * Bisq 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. + * + * Bisq 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 Bisq. If not, see . + */ + +package bisq.core.offer; + +import bisq.core.btc.AddressEntry; +import bisq.core.btc.Restrictions; +import bisq.core.btc.wallet.BsqWalletService; +import bisq.core.btc.wallet.BtcWalletService; +import bisq.core.btc.wallet.TradeWalletService; +import bisq.core.provider.fee.FeeService; +import bisq.core.user.Preferences; +import bisq.core.user.User; + +import org.bitcoinj.core.Address; +import org.bitcoinj.core.Coin; +import org.bitcoinj.core.InsufficientMoneyException; +import org.bitcoinj.core.Transaction; + +import lombok.extern.slf4j.Slf4j; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; + +/** + * Util class for getting a fee estimation. + */ +@Slf4j +public class TxFeeEstimation { + private final BtcWalletService btcWalletService; + private final BsqWalletService bsqWalletService; + private final Preferences preferences; + private final User user; + private final TradeWalletService tradeWalletService; + private final FeeService feeService; + + private int feeTxSize = 260; // size of typical tx with 1 input + private int feeTxSizeEstimationRecursionCounter; + private String offerId; + private OfferPayload.Direction direction; + private Coin amount; + private Coin buyerSecurityDeposit; + private double marketPriceMargin; + private boolean marketPriceAvailable; + + public TxFeeEstimation(BtcWalletService btcWalletService, + BsqWalletService bsqWalletService, + Preferences preferences, + User user, + TradeWalletService tradeWalletService, + FeeService feeService, + String offerId, + OfferPayload.Direction direction, + Coin amount, + Coin buyerSecurityDeposit, + double marketPriceMargin, + boolean marketPriceAvailable, + int feeTxSize) { + + + this.btcWalletService = btcWalletService; + this.bsqWalletService = bsqWalletService; + this.preferences = preferences; + this.user = user; + this.tradeWalletService = tradeWalletService; + this.feeService = feeService; + + this.offerId = offerId; + this.direction = direction; + this.amount = amount; + this.buyerSecurityDeposit = buyerSecurityDeposit; + this.marketPriceMargin = marketPriceMargin; + this.marketPriceAvailable = marketPriceAvailable; + this.feeTxSize = feeTxSize; + } + + public Coin getEstimatedFee() { + Coin sellerSecurityDeposit = Restrictions.getSellerSecurityDeposit(); + Coin txFeeFromFeeService = feeService.getTxFee(feeTxSize); + Address fundingAddress = btcWalletService.getFreshAddressEntry().getAddress(); + Address reservedForTradeAddress = btcWalletService.getOrCreateAddressEntry(offerId, AddressEntry.Context.RESERVED_FOR_TRADE).getAddress(); + Address changeAddress = btcWalletService.getFreshAddressEntry().getAddress(); + + Coin reservedFundsForOffer = OfferUtil.isBuyOffer(direction) ? buyerSecurityDeposit : sellerSecurityDeposit; + if (!OfferUtil.isBuyOffer(direction)) + reservedFundsForOffer = reservedFundsForOffer.add(amount); + + checkNotNull(user.getAcceptedArbitrators(), "user.getAcceptedArbitrators() must not be null"); + checkArgument(!user.getAcceptedArbitrators().isEmpty(), "user.getAcceptedArbitrators() must not be empty"); + String dummyArbitratorAddress = user.getAcceptedArbitrators().get(0).getBtcAddress(); + try { + log.info("We create a dummy tx to see if our estimated size is in the accepted range. feeTxSize={}," + + " txFee based on feeTxSize: {}, recommended txFee is {} sat/byte", + feeTxSize, txFeeFromFeeService.toFriendlyString(), feeService.getTxFeePerByte()); + Transaction tradeFeeTx = tradeWalletService.estimateBtcTradingFeeTxSize( + fundingAddress, + reservedForTradeAddress, + changeAddress, + reservedFundsForOffer, + true, + OfferUtil.getMakerFee(bsqWalletService, preferences, amount, marketPriceAvailable, marketPriceMargin), + txFeeFromFeeService, + dummyArbitratorAddress); + + final int txSize = tradeFeeTx.bitcoinSerialize().length; + // use feeTxSizeEstimationRecursionCounter to avoid risk for endless loop + if (txSize > feeTxSize * 1.2 && feeTxSizeEstimationRecursionCounter < 10) { + feeTxSizeEstimationRecursionCounter++; + log.info("txSize is {} bytes but feeTxSize used for txFee calculation was {} bytes. We try again with an " + + "adjusted txFee to reach the target tx fee.", txSize, feeTxSize); + feeTxSize = txSize; + txFeeFromFeeService = feeService.getTxFee(feeTxSize); + // lets try again with the adjusted txSize and fee. + getEstimatedFee(); + } else { + log.info("feeTxSize {} bytes", feeTxSize); + log.info("txFee based on estimated size: {}, recommended txFee is {} sat/byte", + txFeeFromFeeService.toFriendlyString(), feeService.getTxFeePerByte()); + } + } catch (InsufficientMoneyException e) { + // If we need to fund from an external wallet we can assume we only have 1 input (260 bytes). + log.warn("We cannot do the fee estimation because there are not enough funds in the wallet. This is expected " + + "if the user pays from an external wallet. In that case we use an estimated tx size of 260 bytes."); + feeTxSize = 260; + txFeeFromFeeService = feeService.getTxFee(feeTxSize); + log.info("feeTxSize {} bytes", feeTxSize); + log.info("txFee based on estimated size: {}, recommended txFee is {} sat/byte", + txFeeFromFeeService.toFriendlyString(), feeService.getTxFeePerByte()); + } + + return txFeeFromFeeService; + } +} + diff --git a/core/src/main/java/bisq/core/offer/placeoffer/tasks/ValidateOffer.java b/core/src/main/java/bisq/core/offer/placeoffer/tasks/ValidateOffer.java index efe6040d369..8216c924f21 100644 --- a/core/src/main/java/bisq/core/offer/placeoffer/tasks/ValidateOffer.java +++ b/core/src/main/java/bisq/core/offer/placeoffer/tasks/ValidateOffer.java @@ -98,6 +98,7 @@ protected void run() { "maxTradePeriod must be positive. maxTradePeriod=" + offer.getMaxTradePeriod()); // TODO check upper and lower bounds for fiat // TODO check rest of new parameters + // TODO check for account age witness base tradeLimit is missing complete(); } catch (Exception e) { diff --git a/core/src/main/java/bisq/core/payment/AccountAgeWitnessService.java b/core/src/main/java/bisq/core/payment/AccountAgeWitnessService.java index 38cac92d482..84adb747ae0 100644 --- a/core/src/main/java/bisq/core/payment/AccountAgeWitnessService.java +++ b/core/src/main/java/bisq/core/payment/AccountAgeWitnessService.java @@ -263,8 +263,10 @@ public long getMyAccountAge(PaymentAccountPayload paymentAccountPayload) { public long getMyTradeLimit(PaymentAccount paymentAccount, String currencyCode) { final Optional witnessOptional = Optional.of(getMyWitness(paymentAccount.getPaymentAccountPayload())); - return getTradeLimit(paymentAccount.getPaymentMethod() - .getMaxTradeLimitAsCoin(currencyCode), currencyCode, witnessOptional, new Date()); + return getTradeLimit(paymentAccount.getPaymentMethod().getMaxTradeLimitAsCoin(currencyCode), + currencyCode, + witnessOptional, + new Date()); } diff --git a/core/src/main/java/bisq/core/presentation/BalancePresentation.java b/core/src/main/java/bisq/core/presentation/BalancePresentation.java index 3ddd94cb16b..3b42d19ac6a 100644 --- a/core/src/main/java/bisq/core/presentation/BalancePresentation.java +++ b/core/src/main/java/bisq/core/presentation/BalancePresentation.java @@ -17,7 +17,7 @@ package bisq.core.presentation; -import bisq.core.btc.BalanceModel; +import bisq.core.btc.Balances; import bisq.core.util.BSFormatter; import javax.inject.Inject; @@ -26,7 +26,9 @@ import javafx.beans.property.StringProperty; import lombok.Getter; +import lombok.extern.slf4j.Slf4j; +@Slf4j public class BalancePresentation { @Getter private final StringProperty availableBalance = new SimpleStringProperty(); @@ -36,8 +38,8 @@ public class BalancePresentation { private final StringProperty lockedBalance = new SimpleStringProperty(); @Inject - public BalancePresentation(BalanceModel balanceModel, BSFormatter formatter) { - balanceModel.getAvailableBalance().addListener((observable, oldValue, newValue) -> { + public BalancePresentation(Balances balances, BSFormatter formatter) { + balances.getAvailableBalance().addListener((observable, oldValue, newValue) -> { String value = formatter.formatCoinWithCode(newValue); // If we get full precision the BTC postfix breaks layout so we omit it if (value.length() > 11) @@ -45,10 +47,10 @@ public BalancePresentation(BalanceModel balanceModel, BSFormatter formatter) { availableBalance.set(value); }); - balanceModel.getReservedBalance().addListener((observable, oldValue, newValue) -> { + balances.getReservedBalance().addListener((observable, oldValue, newValue) -> { reservedBalance.set(formatter.formatCoinWithCode(newValue)); }); - balanceModel.getLockedBalance().addListener((observable, oldValue, newValue) -> { + balances.getLockedBalance().addListener((observable, oldValue, newValue) -> { lockedBalance.set(formatter.formatCoinWithCode(newValue)); }); } diff --git a/core/src/main/java/bisq/core/trade/TradeManager.java b/core/src/main/java/bisq/core/trade/TradeManager.java index 14c8627b07f..7af2de39513 100644 --- a/core/src/main/java/bisq/core/trade/TradeManager.java +++ b/core/src/main/java/bisq/core/trade/TradeManager.java @@ -235,6 +235,13 @@ public void onUpdatedDataReceived() { tradableList.getList().addListener((ListChangeListener) change -> onTradesChanged()); onTradesChanged(); + + getAddressEntriesForAvailableFundsStream() + .filter(addressEntry -> addressEntry.getOfferId() != null) + .forEach(addressEntry -> { + log.debug("swapPendingOfferFundingEntries, offerId={}, OFFER_FUNDING", addressEntry.getOfferId()); + btcWalletService.swapTradeEntryToAvailableEntry(addressEntry.getOfferId(), AddressEntry.Context.OFFER_FUNDING); + }); } public void shutDown() { @@ -573,7 +580,7 @@ public Optional getTradeById(String tradeId) { return tradableList.stream().filter(e -> e.getId().equals(tradeId)).findFirst(); } - public Stream getAddressEntriesForAvailableBalanceStream() { + public Stream getAddressEntriesForAvailableFundsStream() { Stream availableOrPayout = Stream.concat(btcWalletService.getAddressEntries(AddressEntry.Context.TRADE_PAYOUT) .stream(), btcWalletService.getFundedAvailableAddressEntries().stream()); Stream available = Stream.concat(availableOrPayout, diff --git a/desktop/src/main/java/bisq/desktop/app/BisqAppMain.java b/desktop/src/main/java/bisq/desktop/app/BisqAppMain.java index 1d519806758..d81cafb0a59 100644 --- a/desktop/src/main/java/bisq/desktop/app/BisqAppMain.java +++ b/desktop/src/main/java/bisq/desktop/app/BisqAppMain.java @@ -54,6 +54,10 @@ public static void main(String[] args) throws Exception { } } + @Override + public void onSetupComplete() { + log.info("onSetupComplete"); + } /////////////////////////////////////////////////////////////////////////////////////////// // First synchronous execution tasks diff --git a/desktop/src/main/java/bisq/desktop/main/funds/withdrawal/WithdrawalView.java b/desktop/src/main/java/bisq/desktop/main/funds/withdrawal/WithdrawalView.java index 441879124a0..514b17d2fac 100644 --- a/desktop/src/main/java/bisq/desktop/main/funds/withdrawal/WithdrawalView.java +++ b/desktop/src/main/java/bisq/desktop/main/funds/withdrawal/WithdrawalView.java @@ -423,7 +423,7 @@ private void openBlockExplorer(WithdrawalListItem item) { private void updateList() { observableList.forEach(WithdrawalListItem::cleanup); - observableList.setAll(tradeManager.getAddressEntriesForAvailableBalanceStream() + observableList.setAll(tradeManager.getAddressEntriesForAvailableFundsStream() .map(addressEntry -> new WithdrawalListItem(addressEntry, walletService, formatter)) .collect(Collectors.toList()));