diff --git a/core/src/main/java/bisq/core/offer/CreateOfferService.java b/core/src/main/java/bisq/core/offer/CreateOfferService.java new file mode 100644 index 00000000000..35475e4d77e --- /dev/null +++ b/core/src/main/java/bisq/core/offer/CreateOfferService.java @@ -0,0 +1,350 @@ +/* + * 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.account.witness.AccountAgeWitnessService; +import bisq.core.btc.TxFeeEstimationService; +import bisq.core.btc.wallet.BsqWalletService; +import bisq.core.btc.wallet.BtcWalletService; +import bisq.core.btc.wallet.Restrictions; +import bisq.core.filter.FilterManager; +import bisq.core.locale.CurrencyUtil; +import bisq.core.locale.Res; +import bisq.core.monetary.Price; +import bisq.core.payment.HalCashAccount; +import bisq.core.payment.PaymentAccount; +import bisq.core.payment.PaymentAccountUtil; +import bisq.core.provider.price.MarketPrice; +import bisq.core.provider.price.PriceFeedService; +import bisq.core.trade.statistics.ReferralIdService; +import bisq.core.user.Preferences; +import bisq.core.user.User; +import bisq.core.util.CoinUtil; + +import bisq.network.p2p.NodeAddress; +import bisq.network.p2p.P2PService; + +import bisq.common.app.Version; +import bisq.common.crypto.PubKeyRing; +import bisq.common.util.Tuple2; +import bisq.common.util.Utilities; + +import org.bitcoinj.core.Coin; + +import javax.inject.Inject; +import javax.inject.Singleton; + +import com.google.common.collect.Lists; + +import java.util.ArrayList; +import java.util.Date; +import java.util.List; +import java.util.Map; +import java.util.UUID; + +import lombok.extern.slf4j.Slf4j; + +@Slf4j +@Singleton +public class CreateOfferService { + private final TxFeeEstimationService txFeeEstimationService; + private final MakerFeeProvider makerFeeProvider; + private final BsqWalletService bsqWalletService; + private final Preferences preferences; + private final PriceFeedService priceFeedService; + private final AccountAgeWitnessService accountAgeWitnessService; + private final ReferralIdService referralIdService; + private final FilterManager filterManager; + private final P2PService p2PService; + private final PubKeyRing pubKeyRing; + private final User user; + private final BtcWalletService btcWalletService; + + + /////////////////////////////////////////////////////////////////////////////////////////// + // Constructor, Initialization + /////////////////////////////////////////////////////////////////////////////////////////// + + @Inject + public CreateOfferService(TxFeeEstimationService txFeeEstimationService, + MakerFeeProvider makerFeeProvider, + BsqWalletService bsqWalletService, + Preferences preferences, + PriceFeedService priceFeedService, + AccountAgeWitnessService accountAgeWitnessService, + ReferralIdService referralIdService, + FilterManager filterManager, + P2PService p2PService, + PubKeyRing pubKeyRing, + User user, + BtcWalletService btcWalletService) { + this.txFeeEstimationService = txFeeEstimationService; + this.makerFeeProvider = makerFeeProvider; + this.bsqWalletService = bsqWalletService; + this.preferences = preferences; + this.priceFeedService = priceFeedService; + this.accountAgeWitnessService = accountAgeWitnessService; + this.referralIdService = referralIdService; + this.filterManager = filterManager; + this.p2PService = p2PService; + this.pubKeyRing = pubKeyRing; + this.user = user; + this.btcWalletService = btcWalletService; + } + + + /////////////////////////////////////////////////////////////////////////////////////////// + // API + /////////////////////////////////////////////////////////////////////////////////////////// + + public String getRandomOfferId() { + return Utilities.getRandomPrefix(5, 8) + "-" + + UUID.randomUUID().toString() + "-" + + Version.VERSION.replace(".", ""); + } + + public Offer createAndGetOffer(String offerId, + OfferPayload.Direction direction, + String currencyCode, + Coin amount, + Coin minAmount, + Price price, + boolean useMarketBasedPrice, + double marketPriceMargin, + double buyerSecurityDepositAsDouble, + PaymentAccount paymentAccount) { + + log.info("offerId={}, \n" + + "currencyCode={}, \n" + + "direction={}, \n" + + "price={}, \n" + + "useMarketBasedPrice={}, \n" + + "marketPriceMargin={}, \n" + + "amount={}, \n" + + "minAmount={}, \n" + + "buyerSecurityDeposit={}, \n" + + "paymentAccount={}, \n", + offerId, currencyCode, direction, price.getValue(), useMarketBasedPrice, marketPriceMargin, + amount.value, minAmount.value, buyerSecurityDepositAsDouble, paymentAccount); + + // prints our param list for dev testing api + log.info("{} " + + "{} " + + "{} " + + "{} " + + "{} " + + "{} " + + "{} " + + "{} " + + "{} " + + "{}", + offerId, currencyCode, direction.name(), price.getValue(), useMarketBasedPrice, marketPriceMargin, + amount.value, minAmount.value, buyerSecurityDepositAsDouble, paymentAccount.getId()); + + long creationTime = new Date().getTime(); + NodeAddress makerAddress = p2PService.getAddress(); + boolean useMarketBasedPriceValue = useMarketBasedPrice && + isMarketPriceAvailable(currencyCode) && + !isHalCashAccount(paymentAccount); + + long priceAsLong = price != null && !useMarketBasedPriceValue ? price.getValue() : 0L; + double marketPriceMarginParam = useMarketBasedPriceValue ? marketPriceMargin : 0; + long amountAsLong = amount != null ? amount.getValue() : 0L; + long minAmountAsLong = minAmount != null ? minAmount.getValue() : 0L; + boolean isCryptoCurrency = CurrencyUtil.isCryptoCurrency(currencyCode); + String baseCurrencyCode = isCryptoCurrency ? currencyCode : Res.getBaseCurrencyCode(); + String counterCurrencyCode = isCryptoCurrency ? Res.getBaseCurrencyCode() : currencyCode; + List acceptedArbitratorAddresses = user.getAcceptedArbitratorAddresses(); + ArrayList arbitratorNodeAddresses = acceptedArbitratorAddresses != null ? + Lists.newArrayList(acceptedArbitratorAddresses) : + new ArrayList<>(); + List acceptedMediatorAddresses = user.getAcceptedMediatorAddresses(); + ArrayList mediatorNodeAddresses = acceptedMediatorAddresses != null ? + Lists.newArrayList(acceptedMediatorAddresses) : + new ArrayList<>(); + String countryCode = PaymentAccountUtil.getCountryCode(paymentAccount); + List acceptedCountryCodes = PaymentAccountUtil.getAcceptedCountryCodes(paymentAccount); + String bankId = PaymentAccountUtil.getBankId(paymentAccount); + List acceptedBanks = PaymentAccountUtil.getAcceptedBanks(paymentAccount); + double sellerSecurityDeposit = getSellerSecurityDepositAsDouble(); + Coin txFeeFromFeeService = getEstimatedFeeAndTxSize(amount, direction, buyerSecurityDepositAsDouble, sellerSecurityDeposit).first; + Coin makerFeeAsCoin = getMakerFee(amount); + boolean isCurrencyForMakerFeeBtc = OfferUtil.isCurrencyForMakerFeeBtc(preferences, bsqWalletService, amount); + Coin buyerSecurityDepositAsCoin = getBuyerSecurityDeposit(amount, buyerSecurityDepositAsDouble); + Coin sellerSecurityDepositAsCoin = getSellerSecurityDeposit(amount, sellerSecurityDeposit); + long maxTradeLimit = getMaxTradeLimit(paymentAccount, currencyCode, direction); + long maxTradePeriod = paymentAccount.getMaxTradePeriod(); + + // reserved for future use cases + // Use null values if not set + boolean isPrivateOffer = false; + boolean useAutoClose = false; + boolean useReOpenAfterAutoClose = false; + long lowerClosePrice = 0; + long upperClosePrice = 0; + String hashOfChallenge = null; + Map extraDataMap = OfferUtil.getExtraDataMap(accountAgeWitnessService, + referralIdService, + paymentAccount, + currencyCode, + preferences); + + OfferUtil.validateOfferData(filterManager, + p2PService, + buyerSecurityDepositAsDouble, + paymentAccount, + currencyCode, + makerFeeAsCoin); + + OfferPayload offerPayload = new OfferPayload(offerId, + creationTime, + makerAddress, + pubKeyRing, + OfferPayload.Direction.valueOf(direction.name()), + priceAsLong, + marketPriceMarginParam, + useMarketBasedPriceValue, + amountAsLong, + minAmountAsLong, + baseCurrencyCode, + counterCurrencyCode, + arbitratorNodeAddresses, + mediatorNodeAddresses, + paymentAccount.getPaymentMethod().getId(), + paymentAccount.getId(), + null, + countryCode, + acceptedCountryCodes, + bankId, + acceptedBanks, + Version.VERSION, + btcWalletService.getLastBlockSeenHeight(), + txFeeFromFeeService.value, + makerFeeAsCoin.value, + isCurrencyForMakerFeeBtc, + buyerSecurityDepositAsCoin.value, + sellerSecurityDepositAsCoin.value, + maxTradeLimit, + maxTradePeriod, + useAutoClose, + useReOpenAfterAutoClose, + upperClosePrice, + lowerClosePrice, + isPrivateOffer, + hashOfChallenge, + extraDataMap, + Version.TRADE_PROTOCOL_VERSION); + Offer offer = new Offer(offerPayload); + offer.setPriceFeedService(priceFeedService); + return offer; + } + + public Tuple2 getEstimatedFeeAndTxSize(Coin amount, + OfferPayload.Direction direction, + double buyerSecurityDeposit, + double sellerSecurityDeposit) { + Coin reservedFundsForOffer = getReservedFundsForOffer(direction, amount, buyerSecurityDeposit, sellerSecurityDeposit); + return txFeeEstimationService.getEstimatedFeeAndTxSizeForMaker(reservedFundsForOffer, getMakerFee(amount)); + } + + public Coin getReservedFundsForOffer(OfferPayload.Direction direction, + Coin amount, + double buyerSecurityDeposit, + double sellerSecurityDeposit) { + + Coin reservedFundsForOffer = getSecurityDeposit(direction, + amount, + buyerSecurityDeposit, + sellerSecurityDeposit); + if (!isBuyOffer(direction)) + reservedFundsForOffer = reservedFundsForOffer.add(amount); + + return reservedFundsForOffer; + } + + public Coin getSecurityDeposit(OfferPayload.Direction direction, + Coin amount, + double buyerSecurityDeposit, + double sellerSecurityDeposit) { + return isBuyOffer(direction) ? + getBuyerSecurityDeposit(amount, buyerSecurityDeposit) : + getSellerSecurityDeposit(amount, sellerSecurityDeposit); + } + + public double getSellerSecurityDepositAsDouble() { + return Restrictions.getSellerSecurityDepositAsPercent(); + } + + public Coin getMakerFee(Coin amount) { + return makerFeeProvider.getMakerFee(bsqWalletService, preferences, amount); + } + + public long getMaxTradeLimit(PaymentAccount paymentAccount, + String currencyCode, + OfferPayload.Direction direction) { + if (paymentAccount != null) { + return accountAgeWitnessService.getMyTradeLimit(paymentAccount, currencyCode, direction); + } else { + return 0; + } + } + + public boolean isBuyOffer(OfferPayload.Direction direction) { + return OfferUtil.isBuyOffer(direction); + } + + + /////////////////////////////////////////////////////////////////////////////////////////// + // Private + /////////////////////////////////////////////////////////////////////////////////////////// + + private boolean isMarketPriceAvailable(String currencyCode) { + MarketPrice marketPrice = priceFeedService.getMarketPrice(currencyCode); + return marketPrice != null && marketPrice.isExternallyProvidedPrice(); + } + + private boolean isHalCashAccount(PaymentAccount paymentAccount) { + return paymentAccount instanceof HalCashAccount; + } + + private Coin getBuyerSecurityDeposit(Coin amount, double buyerSecurityDeposit) { + Coin percentOfAmountAsCoin = CoinUtil.getPercentOfAmountAsCoin(buyerSecurityDeposit, amount); + return getBoundedBuyerSecurityDeposit(percentOfAmountAsCoin); + } + + private Coin getSellerSecurityDeposit(Coin amount, double sellerSecurityDeposit) { + Coin amountAsCoin = amount; + if (amountAsCoin == null) + amountAsCoin = Coin.ZERO; + + Coin percentOfAmountAsCoin = CoinUtil.getPercentOfAmountAsCoin(sellerSecurityDeposit, amountAsCoin); + return getBoundedSellerSecurityDeposit(percentOfAmountAsCoin); + } + + private Coin getBoundedBuyerSecurityDeposit(Coin value) { + // We need to ensure that for small amount values we don't get a too low BTC amount. We limit it with using the + // MinBuyerSecurityDepositAsCoin from Restrictions. + return Coin.valueOf(Math.max(Restrictions.getMinBuyerSecurityDepositAsCoin().value, value.value)); + } + + private Coin getBoundedSellerSecurityDeposit(Coin value) { + // We need to ensure that for small amount values we don't get a too low BTC amount. We limit it with using the + // MinSellerSecurityDepositAsCoin from Restrictions. + return Coin.valueOf(Math.max(Restrictions.getMinSellerSecurityDepositAsCoin().value, value.value)); + } +} diff --git a/core/src/main/java/bisq/core/offer/MakerFeeProvider.java b/core/src/main/java/bisq/core/offer/MakerFeeProvider.java new file mode 100644 index 00000000000..dc2d0fbd7f2 --- /dev/null +++ b/core/src/main/java/bisq/core/offer/MakerFeeProvider.java @@ -0,0 +1,29 @@ +/* + * 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.wallet.BsqWalletService; +import bisq.core.user.Preferences; + +import org.bitcoinj.core.Coin; + +public class MakerFeeProvider { + public Coin getMakerFee(BsqWalletService bsqWalletService, Preferences preferences, Coin amount) { + return OfferUtil.getMakerFee(bsqWalletService, preferences, amount); + } +} diff --git a/core/src/main/java/bisq/core/offer/OpenOfferManager.java b/core/src/main/java/bisq/core/offer/OpenOfferManager.java index 1212a7313e2..bc0c3c8fb0e 100644 --- a/core/src/main/java/bisq/core/offer/OpenOfferManager.java +++ b/core/src/main/java/bisq/core/offer/OpenOfferManager.java @@ -94,6 +94,7 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe private static final long REPUBLISH_INTERVAL_MS = TimeUnit.MINUTES.toMillis(40); private static final long REFRESH_INTERVAL_MS = TimeUnit.MINUTES.toMillis(6); + private final CreateOfferService createOfferService; private final KeyRing keyRing; private final User user; private final P2PService p2PService; @@ -121,7 +122,8 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe /////////////////////////////////////////////////////////////////////////////////////////// @Inject - public OpenOfferManager(KeyRing keyRing, + public OpenOfferManager(CreateOfferService createOfferService, + KeyRing keyRing, User user, P2PService p2PService, BtcWalletService btcWalletService, @@ -137,6 +139,7 @@ public OpenOfferManager(KeyRing keyRing, RefundAgentManager refundAgentManager, DaoFacade daoFacade, Storage> storage) { + this.createOfferService = createOfferService; this.keyRing = keyRing; this.user = user; this.p2PService = p2PService; @@ -335,10 +338,17 @@ public void onAwakeFromStandby() { /////////////////////////////////////////////////////////////////////////////////////////// public void placeOffer(Offer offer, - Coin reservedFundsForOffer, + double buyerSecurityDeposit, boolean useSavingsWallet, TransactionResultHandler resultHandler, ErrorMessageHandler errorMessageHandler) { + checkNotNull(offer.getMakerFee(), "makerFee must not be null"); + + Coin reservedFundsForOffer = createOfferService.getReservedFundsForOffer(offer.getDirection(), + offer.getAmount(), + buyerSecurityDeposit, + createOfferService.getSellerSecurityDepositAsDouble()); + PlaceOfferModel model = new PlaceOfferModel(offer, reservedFundsForOffer, useSavingsWallet, diff --git a/core/src/main/java/bisq/core/trade/statistics/TradeStatistics2.java b/core/src/main/java/bisq/core/trade/statistics/TradeStatistics2.java index feb0b6ed14d..601284f4e74 100644 --- a/core/src/main/java/bisq/core/trade/statistics/TradeStatistics2.java +++ b/core/src/main/java/bisq/core/trade/statistics/TradeStatistics2.java @@ -171,8 +171,7 @@ public byte[] createHash() { return Hash.getSha256Ripemd160hash(Utilities.objectToJson(this).getBytes(Charsets.UTF_8)); } - @Override - public protobuf.PersistableNetworkPayload toProtoMessage() { + private protobuf.TradeStatistics2.Builder getBuilder() { final protobuf.TradeStatistics2.Builder builder = protobuf.TradeStatistics2.newBuilder() .setDirection(OfferPayload.Direction.toProtoMessage(direction)) .setBaseCurrency(baseCurrency) @@ -190,12 +189,16 @@ public protobuf.PersistableNetworkPayload toProtoMessage() { .setDepositTxId(depositTxId) .setHash(ByteString.copyFrom(hash)); Optional.ofNullable(extraDataMap).ifPresent(builder::putAllExtraData); - return protobuf.PersistableNetworkPayload.newBuilder().setTradeStatistics2(builder).build(); + return builder; } - public protobuf.TradeStatistics2 toProtoTradeStatistics2() { - return toProtoMessage().getTradeStatistics2(); + return getBuilder().build(); + } + + @Override + public protobuf.PersistableNetworkPayload toProtoMessage() { + return protobuf.PersistableNetworkPayload.newBuilder().setTradeStatistics2(getBuilder()).build(); } public static TradeStatistics2 fromProto(protobuf.TradeStatistics2 proto) { diff --git a/core/src/main/resources/btc_regtest.seednodes b/core/src/main/resources/btc_regtest.seednodes index caa87710d68..148e6ecb9e0 100644 --- a/core/src/main/resources/btc_regtest.seednodes +++ b/core/src/main/resources/btc_regtest.seednodes @@ -1,4 +1,4 @@ -# By default developers use either port 2002 or 3002 or both as local seed nodes. If they want to use regtest +# By default developers use either port 2002 or 3002 or both as local seed nodes. If they want to use regtest # with Tor they have to add a program argument to pass the custom onion address of the local Tor seed node. # E.g. --seedNodes=YOUR_ONION.onion:2002 @@ -6,7 +6,7 @@ # 1. Run a seed node with prog args: --bitcoinNetwork=regtest --nodePort=2002 --appName=bisq_seed_node_localhost_YOUR_ONION # 2. Find your local onion address in bisq_seed_node_localhost_YOUR_ONION/regtest/tor/hiddenservice/hostname # 3. Shut down the seed node -# 4. Rename YOUR_ONION at the directory with your local onion address as well as the appName program argument to reflect +# 4. Rename YOUR_ONION at the directory with your local onion address as well as the appName program argument to reflect # the real onion address. # 5. Start the seed node again # 6. Start the Bisq app which wants to connect to that seed node with program argument `--seedNodes=YOUR_ONION.onion:2002` diff --git a/core/src/test/java/bisq/core/offer/OpenOfferManagerTest.java b/core/src/test/java/bisq/core/offer/OpenOfferManagerTest.java index c80ea13e0e1..db2f334ded6 100644 --- a/core/src/test/java/bisq/core/offer/OpenOfferManagerTest.java +++ b/core/src/test/java/bisq/core/offer/OpenOfferManagerTest.java @@ -37,7 +37,7 @@ public void testStartEditOfferForActiveOffer() { when(p2PService.getPeerManager()).thenReturn(mock(PeerManager.class)); - final OpenOfferManager manager = new OpenOfferManager(null, null, p2PService, + final OpenOfferManager manager = new OpenOfferManager(null, null, null, p2PService, null, null, null, offerBookService, null, null, null, null, null, null, null, null, @@ -73,7 +73,7 @@ public void testStartEditOfferForDeactivatedOffer() { when(p2PService.getPeerManager()).thenReturn(mock(PeerManager.class)); - final OpenOfferManager manager = new OpenOfferManager(null, null, p2PService, + final OpenOfferManager manager = new OpenOfferManager(null, null, null, p2PService, null, null, null, offerBookService, null, null, null, null, null, null, null, null, @@ -101,7 +101,7 @@ public void testStartEditOfferForOfferThatIsCurrentlyEdited() { when(p2PService.getPeerManager()).thenReturn(mock(PeerManager.class)); - final OpenOfferManager manager = new OpenOfferManager(null, null, p2PService, + final OpenOfferManager manager = new OpenOfferManager(null, null, null, p2PService, null, null, null, offerBookService, null, null, null, null, null, null, null, null, diff --git a/desktop/src/main/java/bisq/desktop/main/offer/MutableOfferDataModel.java b/desktop/src/main/java/bisq/desktop/main/offer/MutableOfferDataModel.java index 97ff63c8f1c..f28fc1f38fd 100644 --- a/desktop/src/main/java/bisq/desktop/main/offer/MutableOfferDataModel.java +++ b/desktop/src/main/java/bisq/desktop/main/offer/MutableOfferDataModel.java @@ -29,23 +29,20 @@ import bisq.core.btc.wallet.BsqWalletService; import bisq.core.btc.wallet.BtcWalletService; import bisq.core.btc.wallet.Restrictions; -import bisq.core.filter.FilterManager; import bisq.core.locale.CurrencyUtil; -import bisq.core.locale.Res; import bisq.core.locale.TradeCurrency; import bisq.core.monetary.Price; import bisq.core.monetary.Volume; +import bisq.core.offer.CreateOfferService; import bisq.core.offer.Offer; import bisq.core.offer.OfferPayload; import bisq.core.offer.OfferUtil; import bisq.core.offer.OpenOfferManager; import bisq.core.payment.HalCashAccount; import bisq.core.payment.PaymentAccount; -import bisq.core.payment.PaymentAccountUtil; import bisq.core.provider.fee.FeeService; import bisq.core.provider.price.PriceFeedService; import bisq.core.trade.handlers.TransactionResultHandler; -import bisq.core.trade.statistics.ReferralIdService; import bisq.core.user.Preferences; import bisq.core.user.User; import bisq.core.util.BSFormatter; @@ -53,8 +50,6 @@ import bisq.network.p2p.P2PService; -import bisq.common.app.Version; -import bisq.common.crypto.KeyRing; import bisq.common.util.MathUtils; import bisq.common.util.Tuple2; import bisq.common.util.Utilities; @@ -64,8 +59,6 @@ import com.google.inject.Inject; -import com.google.common.collect.Lists; - import javafx.beans.property.BooleanProperty; import javafx.beans.property.DoubleProperty; import javafx.beans.property.ObjectProperty; @@ -83,33 +76,26 @@ import javafx.collections.ObservableList; import javafx.collections.SetChangeListener; -import java.util.Date; import java.util.HashSet; -import java.util.List; -import java.util.Map; import java.util.Optional; -import java.util.UUID; import javax.annotation.Nullable; import static com.google.common.base.Preconditions.checkNotNull; public abstract class MutableOfferDataModel extends OfferDataModel implements BsqBalanceListener { + private final CreateOfferService createOfferService; protected final OpenOfferManager openOfferManager; private final BsqWalletService bsqWalletService; private final Preferences preferences; protected final User user; - private final KeyRing keyRing; private final P2PService p2PService; protected final PriceFeedService priceFeedService; final String shortOfferId; - private final FilterManager filterManager; private final AccountAgeWitnessService accountAgeWitnessService; private final FeeService feeService; - private final TxFeeEstimationService txFeeEstimationService; - private final ReferralIdService referralIdService; private final BSFormatter btcFormatter; - private MakerFeeProvider makerFeeProvider; + private final MakerFeeProvider makerFeeProvider; private final Navigation navigation; private final String offerId; private final BalanceListener btcBalanceListener; @@ -119,10 +105,6 @@ public abstract class MutableOfferDataModel extends OfferDataModel implements Bs protected TradeCurrency tradeCurrency; protected final StringProperty tradeCurrencyCode = new SimpleStringProperty(); protected final BooleanProperty useMarketBasedPrice = new SimpleBooleanProperty(); - //final BooleanProperty isMainNet = new SimpleBooleanProperty(); - //final BooleanProperty isFeeFromFundingTxSufficient = new SimpleBooleanProperty(); - - // final ObjectProperty feeFromFundingTxProperty = new SimpleObjectProperty(Coin.NEGATIVE_SATOSHI); protected final ObjectProperty amount = new SimpleObjectProperty<>(); protected final ObjectProperty minAmount = new SimpleObjectProperty<>(); protected final ObjectProperty price = new SimpleObjectProperty<>(); @@ -131,12 +113,11 @@ public abstract class MutableOfferDataModel extends OfferDataModel implements Bs // Percentage value of buyer security deposit. E.g. 0.01 means 1% of trade amount protected final DoubleProperty buyerSecurityDeposit = new SimpleDoubleProperty(); - protected final DoubleProperty sellerSecurityDeposit = new SimpleDoubleProperty(); protected final ObservableList paymentAccounts = FXCollections.observableArrayList(); protected PaymentAccount paymentAccount; - protected boolean isTabSelected; + boolean isTabSelected; protected double marketPriceMargin = 0; private Coin txFeeFromFeeService = Coin.ZERO; private boolean marketPriceAvailable; @@ -149,76 +130,45 @@ public abstract class MutableOfferDataModel extends OfferDataModel implements Bs /////////////////////////////////////////////////////////////////////////////////////////// @Inject - public MutableOfferDataModel(OpenOfferManager openOfferManager, + public MutableOfferDataModel(CreateOfferService createOfferService, + OpenOfferManager openOfferManager, BtcWalletService btcWalletService, BsqWalletService bsqWalletService, Preferences preferences, User user, - KeyRing keyRing, P2PService p2PService, PriceFeedService priceFeedService, - FilterManager filterManager, AccountAgeWitnessService accountAgeWitnessService, FeeService feeService, - TxFeeEstimationService txFeeEstimationService, - ReferralIdService referralIdService, BSFormatter btcFormatter, MakerFeeProvider makerFeeProvider, Navigation navigation) { super(btcWalletService); + this.createOfferService = createOfferService; this.openOfferManager = openOfferManager; this.bsqWalletService = bsqWalletService; this.preferences = preferences; this.user = user; - this.keyRing = keyRing; this.p2PService = p2PService; this.priceFeedService = priceFeedService; - this.filterManager = filterManager; this.accountAgeWitnessService = accountAgeWitnessService; this.feeService = feeService; - this.txFeeEstimationService = txFeeEstimationService; - this.referralIdService = referralIdService; this.btcFormatter = btcFormatter; this.makerFeeProvider = makerFeeProvider; this.navigation = navigation; - offerId = Utilities.getRandomPrefix(5, 8) + "-" + - UUID.randomUUID().toString() + "-" + - Version.VERSION.replace(".", ""); + offerId = createOfferService.getRandomOfferId(); shortOfferId = Utilities.getShortId(offerId); addressEntry = btcWalletService.getOrCreateAddressEntry(offerId, AddressEntry.Context.OFFER_FUNDING); useMarketBasedPrice.set(preferences.isUsePercentageBasedPrice()); buyerSecurityDeposit.set(preferences.getBuyerSecurityDepositAsPercent(null)); - sellerSecurityDeposit.set(Restrictions.getSellerSecurityDepositAsPercent()); btcBalanceListener = new BalanceListener(getAddressEntry().getAddress()) { @Override public void onBalanceChanged(Coin balance, Transaction tx) { updateBalance(); - - /* if (isMainNet.get()) { - SettableFuture future = blockchainService.requestFee(tx.getHashAsString()); - Futures.addCallback(future, new FutureCallback() { - public void onSuccess(Coin fee) { - UserThread.execute(() -> feeFromFundingTxProperty.set(fee)); - } - - public void onFailure(@NotNull Throwable throwable) { - UserThread.execute(() -> new Popup<>() - .warning("We did not get a response for the request of the mining fee used " + - "in the funding transaction.\n\n" + - "Are you sure you used a sufficiently high fee of at least " + - formatter.formatCoinWithCode(FeePolicy.getMinRequiredFeeForFundingTx()) + "?") - .actionButtonText("Yes, I used a sufficiently high fee.") - .onAction(() -> feeFromFundingTxProperty.set(FeePolicy.getMinRequiredFeeForFundingTx())) - .closeButtonText("No. Let's cancel that payment.") - .onClose(() -> feeFromFundingTxProperty.set(Coin.ZERO)) - .show()); - } - }); - }*/ } }; @@ -322,115 +272,32 @@ void onTabSelected(boolean isSelected) { // UI actions /////////////////////////////////////////////////////////////////////////////////////////// - @SuppressWarnings("ConstantConditions") Offer createAndGetOffer() { - boolean useMarketBasedPriceValue = isUseMarketBasedPriceValue(); - long priceAsLong = price.get() != null && !useMarketBasedPriceValue ? price.get().getValue() : 0L; - String currencyCode = tradeCurrencyCode.get(); - boolean isCryptoCurrency = CurrencyUtil.isCryptoCurrency(currencyCode); - String baseCurrencyCode = isCryptoCurrency ? currencyCode : Res.getBaseCurrencyCode(); - String counterCurrencyCode = isCryptoCurrency ? Res.getBaseCurrencyCode() : currencyCode; - - double marketPriceMarginParam = useMarketBasedPriceValue ? marketPriceMargin : 0; - long amountAsLong = this.amount.get() != null ? this.amount.get().getValue() : 0L; - long minAmountAsLong = this.minAmount.get() != null ? this.minAmount.get().getValue() : 0L; - - List acceptedCountryCodes = PaymentAccountUtil.getAcceptedCountryCodes(paymentAccount); - List acceptedBanks = PaymentAccountUtil.getAcceptedBanks(paymentAccount); - String bankId = PaymentAccountUtil.getBankId(paymentAccount); - String countryCode = PaymentAccountUtil.getCountryCode(paymentAccount); - - long maxTradeLimit = getMaxTradeLimit(); - long maxTradePeriod = paymentAccount.getMaxTradePeriod(); - - // reserved for future use cases - // Use null values if not set - boolean isPrivateOffer = false; - boolean useAutoClose = false; - boolean useReOpenAfterAutoClose = false; - long lowerClosePrice = 0; - long upperClosePrice = 0; - String hashOfChallenge = null; - - Coin makerFeeAsCoin = getMakerFee(); - - Map extraDataMap = OfferUtil.getExtraDataMap(accountAgeWitnessService, - referralIdService, - paymentAccount, - currencyCode, - preferences); - - OfferUtil.validateOfferData(filterManager, - p2PService, + return createOfferService.createAndGetOffer(offerId, + direction, + tradeCurrencyCode.get(), + amount.get(), + minAmount.get(), + price.get(), + useMarketBasedPrice.get(), + marketPriceMargin, buyerSecurityDeposit.get(), - paymentAccount, - currencyCode, - makerFeeAsCoin); - - OfferPayload offerPayload = new OfferPayload(offerId, - new Date().getTime(), - p2PService.getAddress(), - keyRing.getPubKeyRing(), - OfferPayload.Direction.valueOf(direction.name()), - priceAsLong, - marketPriceMarginParam, - useMarketBasedPriceValue, - amountAsLong, - minAmountAsLong, - baseCurrencyCode, - counterCurrencyCode, - Lists.newArrayList(user.getAcceptedArbitratorAddresses()), - Lists.newArrayList(user.getAcceptedMediatorAddresses()), - paymentAccount.getPaymentMethod().getId(), - paymentAccount.getId(), - null, - countryCode, - acceptedCountryCodes, - bankId, - acceptedBanks, - Version.VERSION, - btcWalletService.getLastBlockSeenHeight(), - txFeeFromFeeService.value, - makerFeeAsCoin.value, - isCurrencyForMakerFeeBtc(), - getBuyerSecurityDepositAsCoin().value, - getSellerSecurityDepositAsCoin().value, - maxTradeLimit, - maxTradePeriod, - useAutoClose, - useReOpenAfterAutoClose, - upperClosePrice, - lowerClosePrice, - isPrivateOffer, - hashOfChallenge, - extraDataMap, - Version.TRADE_PROTOCOL_VERSION); - Offer offer = new Offer(offerPayload); - offer.setPriceFeedService(priceFeedService); - return offer; + paymentAccount); } // This works only if we have already funds in the wallet - public void estimateTxSize() { - Coin reservedFundsForOffer = getSecurityDeposit(); - if (!isBuyOffer()) - reservedFundsForOffer = reservedFundsForOffer.add(amount.get()); - - Tuple2 estimatedFeeAndTxSize = txFeeEstimationService.getEstimatedFeeAndTxSizeForMaker(reservedFundsForOffer, - getMakerFee()); + public void updateEstimatedFeeAndTxSize() { + Tuple2 estimatedFeeAndTxSize = createOfferService.getEstimatedFeeAndTxSize(amount.get(), + direction, + buyerSecurityDeposit.get(), + createOfferService.getSellerSecurityDepositAsDouble()); txFeeFromFeeService = estimatedFeeAndTxSize.first; feeTxSize = estimatedFeeAndTxSize.second; } void onPlaceOffer(Offer offer, TransactionResultHandler resultHandler) { - checkNotNull(getMakerFee(), "makerFee must not be null"); - - Coin reservedFundsForOffer = getSecurityDeposit(); - if (!isBuyOffer()) - reservedFundsForOffer = reservedFundsForOffer.add(amount.get()); - openOfferManager.placeOffer(offer, - reservedFundsForOffer, + buyerSecurityDeposit.get(), useSavingsWallet, resultHandler, log::error); @@ -539,7 +406,6 @@ void setPreferredCurrencyForMakerFeeBtc(boolean preferredCurrencyForMakerFeeBtc) // Getters /////////////////////////////////////////////////////////////////////////////////////////// - @SuppressWarnings("BooleanMethodIsAlwaysInverted") boolean isMinAmountLessOrEqualAmount() { //noinspection SimplifiableIfStatement if (minAmount.get() != null && amount.get() != null) @@ -700,7 +566,7 @@ public Coin getTxFee() { return txFeeFromFeeService.subtract(getMakerFee()); } - public void swapTradeToSavings() { + void swapTradeToSavings() { btcWalletService.resetAddressEntriesForOpenOffer(offerId); } @@ -775,12 +641,12 @@ protected Coin getBuyerSecurityDepositAsCoin() { return getBoundedBuyerSecurityDepositAsCoin(percentOfAmountAsCoin); } - Coin getSellerSecurityDepositAsCoin() { + private Coin getSellerSecurityDepositAsCoin() { Coin amountAsCoin = this.amount.get(); if (amountAsCoin == null) amountAsCoin = Coin.ZERO; - Coin percentOfAmountAsCoin = CoinUtil.getPercentOfAmountAsCoin(sellerSecurityDeposit.get(), amountAsCoin); + Coin percentOfAmountAsCoin = CoinUtil.getPercentOfAmountAsCoin(createOfferService.getSellerSecurityDepositAsDouble(), amountAsCoin); return getBoundedSellerSecurityDepositAsCoin(percentOfAmountAsCoin); } @@ -800,7 +666,7 @@ ReadOnlyObjectProperty totalToPayAsCoinProperty() { return totalToPayAsCoin; } - public Coin getBsqBalance() { + Coin getBsqBalance() { return bsqWalletService.getAvailableConfirmedBalance(); } @@ -828,11 +694,11 @@ public boolean isCurrencyForMakerFeeBtc() { return OfferUtil.isCurrencyForMakerFeeBtc(preferences, bsqWalletService, amount.get()); } - public boolean isPreferredFeeCurrencyBtc() { + boolean isPreferredFeeCurrencyBtc() { return preferences.isPayFeeInBtc(); } - public boolean isBsqForFeeAvailable() { + boolean isBsqForFeeAvailable() { return OfferUtil.isBsqForMakerFeeAvailable(bsqWalletService, amount.get()); } @@ -840,7 +706,7 @@ public boolean isHalCashAccount() { return paymentAccount instanceof HalCashAccount; } - public boolean canPlaceOffer() { + boolean canPlaceOffer() { return GUIUtil.isBootstrappedOrShowPopup(p2PService) && GUIUtil.canCreateOrTakeOfferOrShowPopup(user, navigation); } diff --git a/desktop/src/main/java/bisq/desktop/main/offer/MutableOfferViewModel.java b/desktop/src/main/java/bisq/desktop/main/offer/MutableOfferViewModel.java index 56aaeb922aa..203b8b5d089 100644 --- a/desktop/src/main/java/bisq/desktop/main/offer/MutableOfferViewModel.java +++ b/desktop/src/main/java/bisq/desktop/main/offer/MutableOfferViewModel.java @@ -681,7 +681,7 @@ public void onCurrencySelected(TradeCurrency tradeCurrency) { } void onShowPayFundsScreen(Runnable actionHandler) { - dataModel.estimateTxSize(); + dataModel.updateEstimatedFeeAndTxSize(); dataModel.requestTxFee(actionHandler); showPayFundsScreenDisplayed.set(true); updateSpinnerInfo(); diff --git a/desktop/src/main/java/bisq/desktop/main/offer/createoffer/CreateOfferDataModel.java b/desktop/src/main/java/bisq/desktop/main/offer/createoffer/CreateOfferDataModel.java index 06c2cfb42ea..361ee6694cb 100644 --- a/desktop/src/main/java/bisq/desktop/main/offer/createoffer/CreateOfferDataModel.java +++ b/desktop/src/main/java/bisq/desktop/main/offer/createoffer/CreateOfferDataModel.java @@ -26,22 +26,18 @@ import bisq.desktop.main.offer.MutableOfferDataModel; import bisq.core.account.witness.AccountAgeWitnessService; -import bisq.core.btc.TxFeeEstimationService; import bisq.core.btc.wallet.BsqWalletService; import bisq.core.btc.wallet.BtcWalletService; -import bisq.core.filter.FilterManager; +import bisq.core.offer.CreateOfferService; import bisq.core.offer.OpenOfferManager; import bisq.core.provider.fee.FeeService; import bisq.core.provider.price.PriceFeedService; -import bisq.core.trade.statistics.ReferralIdService; import bisq.core.user.Preferences; import bisq.core.user.User; import bisq.core.util.BSFormatter; import bisq.network.p2p.P2PService; -import bisq.common.crypto.KeyRing; - import com.google.inject.Inject; /** @@ -52,35 +48,29 @@ class CreateOfferDataModel extends MutableOfferDataModel { @Inject - public CreateOfferDataModel(OpenOfferManager openOfferManager, + public CreateOfferDataModel(CreateOfferService createOfferService, + OpenOfferManager openOfferManager, BtcWalletService btcWalletService, BsqWalletService bsqWalletService, Preferences preferences, User user, - KeyRing keyRing, P2PService p2PService, PriceFeedService priceFeedService, - FilterManager filterManager, AccountAgeWitnessService accountAgeWitnessService, FeeService feeService, - TxFeeEstimationService txFeeEstimationService, - ReferralIdService referralIdService, BSFormatter btcFormatter, MakerFeeProvider makerFeeProvider, Navigation navigation) { - super(openOfferManager, + super(createOfferService, + openOfferManager, btcWalletService, bsqWalletService, preferences, user, - keyRing, p2PService, priceFeedService, - filterManager, accountAgeWitnessService, feeService, - txFeeEstimationService, - referralIdService, btcFormatter, makerFeeProvider, navigation); diff --git a/desktop/src/main/java/bisq/desktop/main/portfolio/editoffer/EditOfferDataModel.java b/desktop/src/main/java/bisq/desktop/main/portfolio/editoffer/EditOfferDataModel.java index f5de8a4763b..388dd13a9e6 100644 --- a/desktop/src/main/java/bisq/desktop/main/portfolio/editoffer/EditOfferDataModel.java +++ b/desktop/src/main/java/bisq/desktop/main/portfolio/editoffer/EditOfferDataModel.java @@ -23,13 +23,12 @@ import bisq.desktop.main.offer.MutableOfferDataModel; import bisq.core.account.witness.AccountAgeWitnessService; -import bisq.core.btc.TxFeeEstimationService; import bisq.core.btc.wallet.BsqWalletService; import bisq.core.btc.wallet.BtcWalletService; import bisq.core.btc.wallet.Restrictions; -import bisq.core.filter.FilterManager; import bisq.core.locale.CurrencyUtil; import bisq.core.locale.TradeCurrency; +import bisq.core.offer.CreateOfferService; import bisq.core.offer.Offer; import bisq.core.offer.OfferPayload; import bisq.core.offer.OpenOffer; @@ -38,7 +37,6 @@ import bisq.core.proto.persistable.CorePersistenceProtoResolver; import bisq.core.provider.fee.FeeService; import bisq.core.provider.price.PriceFeedService; -import bisq.core.trade.statistics.ReferralIdService; import bisq.core.user.Preferences; import bisq.core.user.User; import bisq.core.util.BSFormatter; @@ -46,7 +44,6 @@ import bisq.network.p2p.P2PService; -import bisq.common.crypto.KeyRing; import bisq.common.handlers.ErrorMessageHandler; import bisq.common.handlers.ResultHandler; @@ -61,36 +58,30 @@ class EditOfferDataModel extends MutableOfferDataModel { private OpenOffer.State initialState; @Inject - EditOfferDataModel(OpenOfferManager openOfferManager, + EditOfferDataModel(CreateOfferService createOfferService, + OpenOfferManager openOfferManager, BtcWalletService btcWalletService, BsqWalletService bsqWalletService, Preferences preferences, User user, - KeyRing keyRing, P2PService p2PService, PriceFeedService priceFeedService, - FilterManager filterManager, AccountAgeWitnessService accountAgeWitnessService, FeeService feeService, - TxFeeEstimationService txFeeEstimationService, - ReferralIdService referralIdService, BSFormatter btcFormatter, CorePersistenceProtoResolver corePersistenceProtoResolver, MakerFeeProvider makerFeeProvider, Navigation navigation) { - super(openOfferManager, + super(createOfferService, + openOfferManager, btcWalletService, bsqWalletService, preferences, user, - keyRing, p2PService, priceFeedService, - filterManager, accountAgeWitnessService, feeService, - txFeeEstimationService, - referralIdService, btcFormatter, makerFeeProvider, navigation); diff --git a/desktop/src/test/java/bisq/desktop/main/offer/createoffer/CreateOfferDataModelTest.java b/desktop/src/test/java/bisq/desktop/main/offer/createoffer/CreateOfferDataModelTest.java index e588735a914..4d58655dcc9 100644 --- a/desktop/src/test/java/bisq/desktop/main/offer/createoffer/CreateOfferDataModelTest.java +++ b/desktop/src/test/java/bisq/desktop/main/offer/createoffer/CreateOfferDataModelTest.java @@ -2,13 +2,13 @@ import bisq.desktop.main.offer.MakerFeeProvider; -import bisq.core.btc.TxFeeEstimationService; import bisq.core.btc.model.AddressEntry; import bisq.core.btc.wallet.BtcWalletService; import bisq.core.locale.CryptoCurrency; import bisq.core.locale.FiatCurrency; import bisq.core.locale.GlobalSettings; import bisq.core.locale.Res; +import bisq.core.offer.CreateOfferService; import bisq.core.offer.OfferPayload; import bisq.core.payment.ClearXchangeAccount; import bisq.core.payment.PaymentAccount; @@ -21,6 +21,7 @@ import org.bitcoinj.core.Coin; import java.util.HashSet; +import java.util.UUID; import org.junit.Before; import org.junit.Test; @@ -48,20 +49,20 @@ public void setUp() { BtcWalletService btcWalletService = mock(BtcWalletService.class); PriceFeedService priceFeedService = mock(PriceFeedService.class); FeeService feeService = mock(FeeService.class); - TxFeeEstimationService feeEstimationService = mock(TxFeeEstimationService.class); + CreateOfferService createOfferService = mock(CreateOfferService.class); preferences = mock(Preferences.class); user = mock(User.class); when(btcWalletService.getOrCreateAddressEntry(anyString(), any())).thenReturn(addressEntry); when(preferences.isUsePercentageBasedPrice()).thenReturn(true); when(preferences.getBuyerSecurityDepositAsPercent(null)).thenReturn(0.01); + when(createOfferService.getRandomOfferId()).thenReturn(UUID.randomUUID().toString()); makerFeeProvider = mock(MakerFeeProvider.class); - model = new CreateOfferDataModel(null, btcWalletService, + model = new CreateOfferDataModel(createOfferService, null, btcWalletService, null, preferences, user, null, - null, priceFeedService, null, - null, feeService, feeEstimationService, - null, null, makerFeeProvider, null); + priceFeedService, null, + feeService, null, makerFeeProvider, null); } @Test diff --git a/desktop/src/test/java/bisq/desktop/main/offer/createoffer/CreateOfferViewModelTest.java b/desktop/src/test/java/bisq/desktop/main/offer/createoffer/CreateOfferViewModelTest.java index 515126304ca..149f39a892b 100644 --- a/desktop/src/test/java/bisq/desktop/main/offer/createoffer/CreateOfferViewModelTest.java +++ b/desktop/src/test/java/bisq/desktop/main/offer/createoffer/CreateOfferViewModelTest.java @@ -24,7 +24,6 @@ import bisq.desktop.util.validation.SecurityDepositValidator; import bisq.core.account.witness.AccountAgeWitnessService; -import bisq.core.btc.TxFeeEstimationService; import bisq.core.btc.model.AddressEntry; import bisq.core.btc.wallet.BsqWalletService; import bisq.core.btc.wallet.BtcWalletService; @@ -32,6 +31,7 @@ import bisq.core.locale.CryptoCurrency; import bisq.core.locale.GlobalSettings; import bisq.core.locale.Res; +import bisq.core.offer.CreateOfferService; import bisq.core.offer.OfferPayload; import bisq.core.payment.PaymentAccount; import bisq.core.provider.fee.FeeService; @@ -51,6 +51,8 @@ import java.time.Instant; +import java.util.UUID; + import org.junit.Before; import org.junit.Test; @@ -89,7 +91,7 @@ public void setUp() { BsqWalletService bsqWalletService = mock(BsqWalletService.class); SecurityDepositValidator securityDepositValidator = mock(SecurityDepositValidator.class); AccountAgeWitnessService accountAgeWitnessService = mock(AccountAgeWitnessService.class); - TxFeeEstimationService txFeeEstimationService = mock(TxFeeEstimationService.class); + CreateOfferService createOfferService = mock(CreateOfferService.class); when(btcWalletService.getOrCreateAddressEntry(anyString(), any())).thenReturn(addressEntry); when(btcWalletService.getBalanceForAddress(any())).thenReturn(Coin.valueOf(1000L)); @@ -103,11 +105,12 @@ public void setUp() { when(preferences.getUserCountry()).thenReturn(new Country("ES", "Spain", null)); when(bsqFormatter.formatCoin(any())).thenReturn("0"); when(bsqWalletService.getAvailableConfirmedBalance()).thenReturn(Coin.ZERO); + when(createOfferService.getRandomOfferId()).thenReturn(UUID.randomUUID().toString()); - CreateOfferDataModel dataModel = new CreateOfferDataModel(null, btcWalletService, - bsqWalletService, empty, user, null, null, priceFeedService, null, - accountAgeWitnessService, feeService, txFeeEstimationService, - null, bsFormatter, mock(MakerFeeProvider.class), null); + CreateOfferDataModel dataModel = new CreateOfferDataModel(createOfferService, null, btcWalletService, + bsqWalletService, empty, user, null, priceFeedService, + accountAgeWitnessService, feeService, + bsFormatter, mock(MakerFeeProvider.class), null); dataModel.initWithData(OfferPayload.Direction.BUY, new CryptoCurrency("BTC", "bitcoin")); dataModel.activate(); diff --git a/desktop/src/test/java/bisq/desktop/main/portfolio/editoffer/EditOfferDataModelTest.java b/desktop/src/test/java/bisq/desktop/main/portfolio/editoffer/EditOfferDataModelTest.java index d95ba36bc70..2ee6a5bfde4 100644 --- a/desktop/src/test/java/bisq/desktop/main/portfolio/editoffer/EditOfferDataModelTest.java +++ b/desktop/src/test/java/bisq/desktop/main/portfolio/editoffer/EditOfferDataModelTest.java @@ -11,6 +11,7 @@ import bisq.core.locale.CryptoCurrency; import bisq.core.locale.GlobalSettings; import bisq.core.locale.Res; +import bisq.core.offer.CreateOfferService; import bisq.core.offer.OfferPayload; import bisq.core.offer.OpenOffer; import bisq.core.payment.CryptoCurrencyAccount; @@ -32,6 +33,8 @@ import java.time.Instant; +import java.util.UUID; + import org.junit.Before; import org.junit.Rule; import org.junit.Test; @@ -75,6 +78,7 @@ public void setUp() { BsqWalletService bsqWalletService = mock(BsqWalletService.class); SecurityDepositValidator securityDepositValidator = mock(SecurityDepositValidator.class); AccountAgeWitnessService accountAgeWitnessService = mock(AccountAgeWitnessService.class); + CreateOfferService createOfferService = mock(CreateOfferService.class); when(btcWalletService.getOrCreateAddressEntry(anyString(), any())).thenReturn(addressEntry); when(btcWalletService.getBalanceForAddress(any())).thenReturn(Coin.valueOf(1000L)); @@ -88,12 +92,13 @@ public void setUp() { when(preferences.getUserCountry()).thenReturn(new Country("US", "United States", null)); when(bsqFormatter.formatCoin(any())).thenReturn("0"); when(bsqWalletService.getAvailableConfirmedBalance()).thenReturn(Coin.ZERO); + when(createOfferService.getRandomOfferId()).thenReturn(UUID.randomUUID().toString()); - model = new EditOfferDataModel(null, + model = new EditOfferDataModel(createOfferService, null, btcWalletService, bsqWalletService, empty, user, - null, null, priceFeedService, null, + null, priceFeedService, accountAgeWitnessService, feeService, null, null, - null, null, mock(MakerFeeProvider.class), null); + mock(MakerFeeProvider.class), null); } @Test