mirror of
https://github.com/bisq-network/bisq.git
synced 2025-02-23 15:00:30 +01:00
Add isTakerApiUser field to OfferAvailabilityRequest
Add UNCONF_TX_LIMIT_HIT and MAKER_DENIED_API_USER to AvailabilityResult enum Apply handling for api filter features
This commit is contained in:
parent
95063b6c7f
commit
c2174607f5
17 changed files with 395 additions and 260 deletions
|
@ -112,6 +112,15 @@ public class CoreApi {
|
|||
return coreOffersService.getOffers(direction, currencyCode);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param direction The offer direction
|
||||
* @param currencyCode The offer currency
|
||||
* @return Returns the offers which can be taken
|
||||
*/
|
||||
List<Offer> getOffersAvailableForTaker(String direction, String currencyCode) {
|
||||
return coreOffersService.getOffersAvailableForTaker(direction, currencyCode, true);
|
||||
}
|
||||
|
||||
public void createAnPlaceOffer(String currencyCode,
|
||||
String directionAsString,
|
||||
String priceAsString,
|
||||
|
@ -202,6 +211,7 @@ public class CoreApi {
|
|||
coreTradesService.takeOffer(offer,
|
||||
paymentAccountId,
|
||||
takerFeeCurrencyCode,
|
||||
true,
|
||||
resultHandler);
|
||||
}
|
||||
|
||||
|
|
|
@ -22,6 +22,7 @@ import bisq.core.monetary.Price;
|
|||
import bisq.core.offer.CreateOfferService;
|
||||
import bisq.core.offer.Offer;
|
||||
import bisq.core.offer.OfferBookService;
|
||||
import bisq.core.offer.OfferFilter;
|
||||
import bisq.core.offer.OfferUtil;
|
||||
import bisq.core.offer.OpenOfferManager;
|
||||
import bisq.core.payment.PaymentAccount;
|
||||
|
@ -58,20 +59,24 @@ class CoreOffersService {
|
|||
private final OpenOfferManager openOfferManager;
|
||||
private final OfferUtil offerUtil;
|
||||
private final User user;
|
||||
private final OfferFilter offerFilter;
|
||||
|
||||
@Inject
|
||||
public CoreOffersService(CreateOfferService createOfferService,
|
||||
OfferBookService offerBookService,
|
||||
OpenOfferManager openOfferManager,
|
||||
OfferUtil offerUtil,
|
||||
User user) {
|
||||
User user,
|
||||
OfferFilter offerFilter) {
|
||||
this.createOfferService = createOfferService;
|
||||
this.offerBookService = offerBookService;
|
||||
this.openOfferManager = openOfferManager;
|
||||
this.offerUtil = offerUtil;
|
||||
this.user = user;
|
||||
this.offerFilter = offerFilter;
|
||||
}
|
||||
|
||||
// TODO should we add a check for offerFilter.canTakeOffer?
|
||||
Offer getOffer(String id) {
|
||||
return offerBookService.getOffers().stream()
|
||||
.filter(o -> o.getId().equals(id))
|
||||
|
@ -79,6 +84,8 @@ class CoreOffersService {
|
|||
new IllegalStateException(format("offer with id '%s' not found", id)));
|
||||
}
|
||||
|
||||
// TODO returns all offers also those which cannot be taken. Should we use the filter from
|
||||
// getOffersAvailableForTaker here and remove the getOffersAvailableForTaker method?
|
||||
List<Offer> getOffers(String direction, String currencyCode) {
|
||||
List<Offer> offers = offerBookService.getOffers().stream()
|
||||
.filter(o -> {
|
||||
|
@ -99,6 +106,12 @@ class CoreOffersService {
|
|||
return offers;
|
||||
}
|
||||
|
||||
List<Offer> getOffersAvailableForTaker(String direction, String currencyCode, boolean isTakerApiUser) {
|
||||
return getOffers(direction, currencyCode).stream()
|
||||
.filter(offer -> offerFilter.canTakeOffer(offer, isTakerApiUser).isValid())
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
// Create and place new offer.
|
||||
void createAndPlaceOffer(String currencyCode,
|
||||
String directionAsString,
|
||||
|
|
|
@ -82,6 +82,7 @@ class CoreTradesService {
|
|||
void takeOffer(Offer offer,
|
||||
String paymentAccountId,
|
||||
String takerFeeCurrencyCode,
|
||||
boolean isTakerApiUser,
|
||||
Consumer<Trade> resultHandler) {
|
||||
coreWalletsService.verifyWalletsAreAvailable();
|
||||
coreWalletsService.verifyEncryptedWalletIsUnlocked();
|
||||
|
@ -108,6 +109,7 @@ class CoreTradesService {
|
|||
offer,
|
||||
paymentAccountId,
|
||||
useSavingsWallet,
|
||||
isTakerApiUser,
|
||||
resultHandler::accept,
|
||||
errorMessage -> {
|
||||
log.error(errorMessage);
|
||||
|
|
|
@ -27,5 +27,7 @@ public enum AvailabilityResult {
|
|||
NO_MEDIATORS,
|
||||
USER_IGNORED,
|
||||
MISSING_MANDATORY_CAPABILITY,
|
||||
NO_REFUND_AGENTS
|
||||
NO_REFUND_AGENTS,
|
||||
UNCONF_TX_LIMIT_HIT,
|
||||
MAKER_DENIED_API_USER
|
||||
}
|
||||
|
|
209
core/src/main/java/bisq/core/offer/OfferFilter.java
Normal file
209
core/src/main/java/bisq/core/offer/OfferFilter.java
Normal file
|
@ -0,0 +1,209 @@
|
|||
/*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package bisq.core.offer;
|
||||
|
||||
import bisq.core.account.witness.AccountAgeWitnessService;
|
||||
import bisq.core.filter.FilterManager;
|
||||
import bisq.core.locale.CurrencyUtil;
|
||||
import bisq.core.payment.PaymentAccount;
|
||||
import bisq.core.payment.PaymentAccountUtil;
|
||||
import bisq.core.user.Preferences;
|
||||
import bisq.core.user.User;
|
||||
|
||||
import bisq.common.app.Version;
|
||||
|
||||
import org.bitcoinj.core.Coin;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Singleton;
|
||||
|
||||
import javafx.collections.SetChangeListener;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
@Slf4j
|
||||
@Singleton
|
||||
public class OfferFilter {
|
||||
private final User user;
|
||||
private final Preferences preferences;
|
||||
private final FilterManager filterManager;
|
||||
private final AccountAgeWitnessService accountAgeWitnessService;
|
||||
private final Map<String, Boolean> insufficientCounterpartyTradeLimitCache = new HashMap<>();
|
||||
private final Map<String, Boolean> myInsufficientTradeLimitCache = new HashMap<>();
|
||||
|
||||
@Inject
|
||||
public OfferFilter(User user,
|
||||
Preferences preferences,
|
||||
FilterManager filterManager,
|
||||
AccountAgeWitnessService accountAgeWitnessService) {
|
||||
this.user = user;
|
||||
this.preferences = preferences;
|
||||
this.filterManager = filterManager;
|
||||
this.accountAgeWitnessService = accountAgeWitnessService;
|
||||
|
||||
if (user != null) {
|
||||
// If our accounts have changed we reset our myInsufficientTradeLimitCache as it depends on account data
|
||||
user.getPaymentAccountsAsObservable().addListener((SetChangeListener<PaymentAccount>) c ->
|
||||
myInsufficientTradeLimitCache.clear());
|
||||
}
|
||||
}
|
||||
|
||||
public enum Result {
|
||||
VALID(true),
|
||||
API_DISABLED,
|
||||
HAS_NO_PAYMENT_ACCOUNT_VALID_FOR_OFFER,
|
||||
HAS_NOT_SAME_PROTOCOL_VERSION,
|
||||
IS_IGNORED,
|
||||
IS_OFFER_BANNED,
|
||||
IS_CURRENCY_BANNED,
|
||||
IS_PAYMENT_METHOD_BANNED,
|
||||
IS_NODE_ADDRESS_BANNED,
|
||||
REQUIRE_UPDATE_TO_NEW_VERSION,
|
||||
IS_INSUFFICIENT_COUNTERPARTY_TRADE_LIMIT,
|
||||
IS_MY_INSUFFICIENT_TRADE_LIMIT;
|
||||
|
||||
@Getter
|
||||
private final boolean isValid;
|
||||
|
||||
Result(boolean isValid) {
|
||||
this.isValid = isValid;
|
||||
}
|
||||
|
||||
Result() {
|
||||
this(false);
|
||||
}
|
||||
}
|
||||
|
||||
public Result canTakeOffer(Offer offer, boolean isTakerApiUser) {
|
||||
if (isTakerApiUser && filterManager.getFilter() != null && filterManager.getFilter().isDisableApi()) {
|
||||
return Result.API_DISABLED;
|
||||
}
|
||||
if (!isAnyPaymentAccountValidForOffer(offer)) {
|
||||
return Result.HAS_NO_PAYMENT_ACCOUNT_VALID_FOR_OFFER;
|
||||
}
|
||||
if (!hasSameProtocolVersion(offer)) {
|
||||
return Result.HAS_NOT_SAME_PROTOCOL_VERSION;
|
||||
}
|
||||
if (isIgnored(offer)) {
|
||||
return Result.IS_IGNORED;
|
||||
}
|
||||
if (isOfferBanned(offer)) {
|
||||
return Result.IS_OFFER_BANNED;
|
||||
}
|
||||
if (isCurrencyBanned(offer)) {
|
||||
return Result.IS_CURRENCY_BANNED;
|
||||
}
|
||||
if (isPaymentMethodBanned(offer)) {
|
||||
return Result.IS_PAYMENT_METHOD_BANNED;
|
||||
}
|
||||
if (isNodeAddressBanned(offer)) {
|
||||
return Result.IS_NODE_ADDRESS_BANNED;
|
||||
}
|
||||
if (requireUpdateToNewVersion()) {
|
||||
return Result.REQUIRE_UPDATE_TO_NEW_VERSION;
|
||||
}
|
||||
if (isInsufficientCounterpartyTradeLimit(offer)) {
|
||||
return Result.IS_INSUFFICIENT_COUNTERPARTY_TRADE_LIMIT;
|
||||
}
|
||||
if (isMyInsufficientTradeLimit(offer)) {
|
||||
return Result.IS_MY_INSUFFICIENT_TRADE_LIMIT;
|
||||
}
|
||||
|
||||
return Result.VALID;
|
||||
}
|
||||
|
||||
public boolean isAnyPaymentAccountValidForOffer(Offer offer) {
|
||||
return user.getPaymentAccounts() != null &&
|
||||
PaymentAccountUtil.isAnyTakerPaymentAccountValidForOffer(offer, user.getPaymentAccounts());
|
||||
}
|
||||
|
||||
public boolean hasSameProtocolVersion(Offer offer) {
|
||||
return offer.getProtocolVersion() == Version.TRADE_PROTOCOL_VERSION;
|
||||
}
|
||||
|
||||
public boolean isIgnored(Offer offer) {
|
||||
return preferences.getIgnoreTradersList().stream()
|
||||
.anyMatch(i -> i.equals(offer.getMakerNodeAddress().getFullAddress()));
|
||||
}
|
||||
|
||||
public boolean isOfferBanned(Offer offer) {
|
||||
return filterManager.isOfferIdBanned(offer.getId());
|
||||
}
|
||||
|
||||
public boolean isCurrencyBanned(Offer offer) {
|
||||
return filterManager.isCurrencyBanned(offer.getCurrencyCode());
|
||||
}
|
||||
|
||||
public boolean isPaymentMethodBanned(Offer offer) {
|
||||
return filterManager.isPaymentMethodBanned(offer.getPaymentMethod());
|
||||
}
|
||||
|
||||
public boolean isNodeAddressBanned(Offer offer) {
|
||||
return filterManager.isNodeAddressBanned(offer.getMakerNodeAddress());
|
||||
}
|
||||
|
||||
public boolean requireUpdateToNewVersion() {
|
||||
return filterManager.requireUpdateToNewVersionForTrading();
|
||||
}
|
||||
|
||||
// This call is a bit expensive so we cache results
|
||||
public boolean isInsufficientCounterpartyTradeLimit(Offer offer) {
|
||||
String offerId = offer.getId();
|
||||
if (insufficientCounterpartyTradeLimitCache.containsKey(offerId)) {
|
||||
return insufficientCounterpartyTradeLimitCache.get(offerId);
|
||||
}
|
||||
|
||||
boolean result = CurrencyUtil.isFiatCurrency(offer.getCurrencyCode()) &&
|
||||
!accountAgeWitnessService.verifyPeersTradeAmount(offer, offer.getAmount(),
|
||||
errorMessage -> {
|
||||
});
|
||||
insufficientCounterpartyTradeLimitCache.put(offerId, result);
|
||||
return result;
|
||||
}
|
||||
|
||||
// This call is a bit expensive so we cache results
|
||||
public boolean isMyInsufficientTradeLimit(Offer offer) {
|
||||
String offerId = offer.getId();
|
||||
if (myInsufficientTradeLimitCache.containsKey(offerId)) {
|
||||
return myInsufficientTradeLimitCache.get(offerId);
|
||||
}
|
||||
|
||||
Optional<PaymentAccount> accountOptional = PaymentAccountUtil.getMostMaturePaymentAccountForOffer(offer,
|
||||
user.getPaymentAccounts(),
|
||||
accountAgeWitnessService);
|
||||
long myTradeLimit = accountOptional
|
||||
.map(paymentAccount -> accountAgeWitnessService.getMyTradeLimit(paymentAccount,
|
||||
offer.getCurrencyCode(), offer.getMirroredDirection()))
|
||||
.orElse(0L);
|
||||
long offerMinAmount = offer.getMinAmount().value;
|
||||
log.debug("isInsufficientTradeLimit accountOptional={}, myTradeLimit={}, offerMinAmount={}, ",
|
||||
accountOptional.isPresent() ? accountOptional.get().getAccountName() : "null",
|
||||
Coin.valueOf(myTradeLimit).toFriendlyString(),
|
||||
Coin.valueOf(offerMinAmount).toFriendlyString());
|
||||
boolean result = CurrencyUtil.isFiatCurrency(offer.getCurrencyCode()) &&
|
||||
accountOptional.isPresent() &&
|
||||
myTradeLimit < offerMinAmount;
|
||||
myInsufficientTradeLimitCache.put(offerId, result);
|
||||
return result;
|
||||
}
|
||||
}
|
|
@ -634,47 +634,50 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
|
|||
NodeAddress refundAgentNodeAddress = null;
|
||||
if (openOfferOptional.isPresent()) {
|
||||
OpenOffer openOffer = openOfferOptional.get();
|
||||
if (openOffer.getState() == OpenOffer.State.AVAILABLE) {
|
||||
Offer offer = openOffer.getOffer();
|
||||
if (preferences.getIgnoreTradersList().stream().noneMatch(fullAddress -> fullAddress.equals(peer.getFullAddress()))) {
|
||||
availabilityResult = AvailabilityResult.AVAILABLE;
|
||||
if (!apiUserDeniedByOffer(request)) {
|
||||
if (openOffer.getState() == OpenOffer.State.AVAILABLE) {
|
||||
Offer offer = openOffer.getOffer();
|
||||
if (preferences.getIgnoreTradersList().stream().noneMatch(fullAddress -> fullAddress.equals(peer.getFullAddress()))) {
|
||||
mediatorNodeAddress = DisputeAgentSelection.getLeastUsedMediator(tradeStatisticsManager, mediatorManager).getNodeAddress();
|
||||
openOffer.setMediatorNodeAddress(mediatorNodeAddress);
|
||||
|
||||
mediatorNodeAddress = DisputeAgentSelection.getLeastUsedMediator(tradeStatisticsManager, mediatorManager).getNodeAddress();
|
||||
openOffer.setMediatorNodeAddress(mediatorNodeAddress);
|
||||
refundAgentNodeAddress = DisputeAgentSelection.getLeastUsedRefundAgent(tradeStatisticsManager, refundAgentManager).getNodeAddress();
|
||||
openOffer.setRefundAgentNodeAddress(refundAgentNodeAddress);
|
||||
|
||||
refundAgentNodeAddress = DisputeAgentSelection.getLeastUsedRefundAgent(tradeStatisticsManager, refundAgentManager).getNodeAddress();
|
||||
openOffer.setRefundAgentNodeAddress(refundAgentNodeAddress);
|
||||
|
||||
try {
|
||||
// Check also tradePrice to avoid failures after taker fee is paid caused by a too big difference
|
||||
// in trade price between the peers. Also here poor connectivity might cause market price API connection
|
||||
// losses and therefore an outdated market price.
|
||||
offer.checkTradePriceTolerance(request.getTakersTradePrice());
|
||||
} catch (TradePriceOutOfToleranceException e) {
|
||||
log.warn("Trade price check failed because takers price is outside out tolerance.");
|
||||
availabilityResult = AvailabilityResult.PRICE_OUT_OF_TOLERANCE;
|
||||
} catch (MarketPriceNotAvailableException e) {
|
||||
log.warn(e.getMessage());
|
||||
availabilityResult = AvailabilityResult.MARKET_PRICE_NOT_AVAILABLE;
|
||||
} catch (Throwable e) {
|
||||
log.warn("Trade price check failed. " + e.getMessage());
|
||||
availabilityResult = AvailabilityResult.UNKNOWN_FAILURE;
|
||||
try {
|
||||
// Check also tradePrice to avoid failures after taker fee is paid caused by a too big difference
|
||||
// in trade price between the peers. Also here poor connectivity might cause market price API connection
|
||||
// losses and therefore an outdated market price.
|
||||
offer.checkTradePriceTolerance(request.getTakersTradePrice());
|
||||
availabilityResult = AvailabilityResult.AVAILABLE;
|
||||
} catch (TradePriceOutOfToleranceException e) {
|
||||
log.warn("Trade price check failed because takers price is outside out tolerance.");
|
||||
availabilityResult = AvailabilityResult.PRICE_OUT_OF_TOLERANCE;
|
||||
} catch (MarketPriceNotAvailableException e) {
|
||||
log.warn(e.getMessage());
|
||||
availabilityResult = AvailabilityResult.MARKET_PRICE_NOT_AVAILABLE;
|
||||
} catch (Throwable e) {
|
||||
log.warn("Trade price check failed. " + e.getMessage());
|
||||
availabilityResult = AvailabilityResult.UNKNOWN_FAILURE;
|
||||
}
|
||||
} else {
|
||||
availabilityResult = AvailabilityResult.USER_IGNORED;
|
||||
}
|
||||
} else {
|
||||
availabilityResult = AvailabilityResult.USER_IGNORED;
|
||||
availabilityResult = AvailabilityResult.OFFER_TAKEN;
|
||||
}
|
||||
} else {
|
||||
availabilityResult = AvailabilityResult.OFFER_TAKEN;
|
||||
availabilityResult = AvailabilityResult.MAKER_DENIED_API_USER;
|
||||
}
|
||||
} else {
|
||||
log.warn("handleOfferAvailabilityRequest: openOffer not found. That should never happen.");
|
||||
log.warn("handleOfferAvailabilityRequest: openOffer not found.");
|
||||
availabilityResult = AvailabilityResult.OFFER_TAKEN;
|
||||
}
|
||||
|
||||
if (btcWalletService.isUnconfirmedTransactionsLimitHit() || bsqWalletService.isUnconfirmedTransactionsLimitHit()) {
|
||||
errorMessage = Res.get("shared.unconfirmedTransactionsLimitReached");
|
||||
log.warn(errorMessage);
|
||||
availabilityResult = AvailabilityResult.UNKNOWN_FAILURE;
|
||||
availabilityResult = AvailabilityResult.UNCONF_TX_LIMIT_HIT;
|
||||
}
|
||||
|
||||
OfferAvailabilityResponse offerAvailabilityResponse = new OfferAvailabilityResponse(request.offerId,
|
||||
|
@ -716,6 +719,10 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
|
|||
}
|
||||
}
|
||||
|
||||
private boolean apiUserDeniedByOffer(OfferAvailabilityRequest request) {
|
||||
return preferences.isDenyApiTaker() && request.isTakerApiUser();
|
||||
}
|
||||
|
||||
private void sendAckMessage(OfferAvailabilityRequest message,
|
||||
NodeAddress sender,
|
||||
boolean result,
|
||||
|
|
|
@ -66,19 +66,24 @@ public class OfferAvailabilityModel implements Model {
|
|||
@Getter
|
||||
private NodeAddress selectedRefundAgent;
|
||||
|
||||
// Added in v1.5.5
|
||||
@Getter
|
||||
private final boolean isTakerApiUser;
|
||||
|
||||
public OfferAvailabilityModel(Offer offer,
|
||||
PubKeyRing pubKeyRing,
|
||||
P2PService p2PService,
|
||||
User user,
|
||||
MediatorManager mediatorManager,
|
||||
TradeStatisticsManager tradeStatisticsManager) {
|
||||
TradeStatisticsManager tradeStatisticsManager,
|
||||
boolean isTakerApiUser) {
|
||||
this.offer = offer;
|
||||
this.pubKeyRing = pubKeyRing;
|
||||
this.p2PService = p2PService;
|
||||
this.user = user;
|
||||
this.mediatorManager = mediatorManager;
|
||||
this.tradeStatisticsManager = tradeStatisticsManager;
|
||||
this.isTakerApiUser = isTakerApiUser;
|
||||
}
|
||||
|
||||
public NodeAddress getPeerNodeAddress() {
|
||||
|
|
|
@ -39,7 +39,8 @@ public class SendOfferAvailabilityRequest extends Task<OfferAvailabilityModel> {
|
|||
try {
|
||||
runInterceptHook();
|
||||
|
||||
OfferAvailabilityRequest message = new OfferAvailabilityRequest(model.getOffer().getId(), model.getPubKeyRing(), model.getTakersTradePrice());
|
||||
OfferAvailabilityRequest message = new OfferAvailabilityRequest(model.getOffer().getId(),
|
||||
model.getPubKeyRing(), model.getTakersTradePrice(), model.isTakerApiUser());
|
||||
log.info("Send {} with offerId {} and uid {} to peer {}",
|
||||
message.getClass().getSimpleName(), message.getOfferId(),
|
||||
message.getUid(), model.getPeerNodeAddress());
|
||||
|
|
|
@ -42,13 +42,16 @@ public final class OfferAvailabilityRequest extends OfferMessage implements Supp
|
|||
private final long takersTradePrice;
|
||||
@Nullable
|
||||
private final Capabilities supportedCapabilities;
|
||||
private final boolean isTakerApiUser;
|
||||
|
||||
public OfferAvailabilityRequest(String offerId,
|
||||
PubKeyRing pubKeyRing,
|
||||
long takersTradePrice) {
|
||||
long takersTradePrice,
|
||||
boolean isTakerApiUser) {
|
||||
this(offerId,
|
||||
pubKeyRing,
|
||||
takersTradePrice,
|
||||
isTakerApiUser,
|
||||
Capabilities.app,
|
||||
Version.getP2PMessageVersion(),
|
||||
UUID.randomUUID().toString());
|
||||
|
@ -62,12 +65,14 @@ public final class OfferAvailabilityRequest extends OfferMessage implements Supp
|
|||
private OfferAvailabilityRequest(String offerId,
|
||||
PubKeyRing pubKeyRing,
|
||||
long takersTradePrice,
|
||||
boolean isTakerApiUser,
|
||||
@Nullable Capabilities supportedCapabilities,
|
||||
int messageVersion,
|
||||
@Nullable String uid) {
|
||||
super(messageVersion, offerId, uid);
|
||||
this.pubKeyRing = pubKeyRing;
|
||||
this.takersTradePrice = takersTradePrice;
|
||||
this.isTakerApiUser = isTakerApiUser;
|
||||
this.supportedCapabilities = supportedCapabilities;
|
||||
}
|
||||
|
||||
|
@ -76,7 +81,8 @@ public final class OfferAvailabilityRequest extends OfferMessage implements Supp
|
|||
final protobuf.OfferAvailabilityRequest.Builder builder = protobuf.OfferAvailabilityRequest.newBuilder()
|
||||
.setOfferId(offerId)
|
||||
.setPubKeyRing(pubKeyRing.toProtoMessage())
|
||||
.setTakersTradePrice(takersTradePrice);
|
||||
.setTakersTradePrice(takersTradePrice)
|
||||
.setIsTakerApiUser(isTakerApiUser);
|
||||
|
||||
Optional.ofNullable(supportedCapabilities).ifPresent(e -> builder.addAllSupportedCapabilities(Capabilities.toIntList(supportedCapabilities)));
|
||||
Optional.ofNullable(uid).ifPresent(e -> builder.setUid(uid));
|
||||
|
@ -90,6 +96,7 @@ public final class OfferAvailabilityRequest extends OfferMessage implements Supp
|
|||
return new OfferAvailabilityRequest(proto.getOfferId(),
|
||||
PubKeyRing.fromProto(proto.getPubKeyRing()),
|
||||
proto.getTakersTradePrice(),
|
||||
proto.getIsTakerApiUser(),
|
||||
Capabilities.fromIntList(proto.getSupportedCapabilitiesList()),
|
||||
messageVersion,
|
||||
proto.getUid().isEmpty() ? null : proto.getUid());
|
||||
|
|
|
@ -373,6 +373,7 @@ public class TradeManager implements PersistedDataHost, DecryptedDirectMessageLi
|
|||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
public void checkOfferAvailability(Offer offer,
|
||||
boolean isTakerApiUser,
|
||||
ResultHandler resultHandler,
|
||||
ErrorMessageHandler errorMessageHandler) {
|
||||
if (btcWalletService.isUnconfirmedTransactionsLimitHit() ||
|
||||
|
@ -383,7 +384,7 @@ public class TradeManager implements PersistedDataHost, DecryptedDirectMessageLi
|
|||
return;
|
||||
}
|
||||
|
||||
offer.checkOfferAvailability(getOfferAvailabilityModel(offer), resultHandler, errorMessageHandler);
|
||||
offer.checkOfferAvailability(getOfferAvailabilityModel(offer, isTakerApiUser), resultHandler, errorMessageHandler);
|
||||
}
|
||||
|
||||
// First we check if offer is still available then we create the trade with the protocol
|
||||
|
@ -396,12 +397,13 @@ public class TradeManager implements PersistedDataHost, DecryptedDirectMessageLi
|
|||
Offer offer,
|
||||
String paymentAccountId,
|
||||
boolean useSavingsWallet,
|
||||
boolean isTakerApiUser,
|
||||
TradeResultHandler tradeResultHandler,
|
||||
ErrorMessageHandler errorMessageHandler) {
|
||||
|
||||
checkArgument(!wasOfferAlreadyUsedInTrade(offer.getId()));
|
||||
|
||||
OfferAvailabilityModel model = getOfferAvailabilityModel(offer);
|
||||
OfferAvailabilityModel model = getOfferAvailabilityModel(offer, isTakerApiUser);
|
||||
offer.checkOfferAvailability(model,
|
||||
() -> {
|
||||
if (offer.getState() == Offer.State.AVAILABLE) {
|
||||
|
@ -464,14 +466,15 @@ public class TradeManager implements PersistedDataHost, DecryptedDirectMessageLi
|
|||
processModelServiceProvider.getKeyRing().getPubKeyRing());
|
||||
}
|
||||
|
||||
private OfferAvailabilityModel getOfferAvailabilityModel(Offer offer) {
|
||||
private OfferAvailabilityModel getOfferAvailabilityModel(Offer offer, boolean isTakerApiUser) {
|
||||
return new OfferAvailabilityModel(
|
||||
offer,
|
||||
keyRing.getPubKeyRing(),
|
||||
p2PService,
|
||||
user,
|
||||
mediatorManager,
|
||||
tradeStatisticsManager);
|
||||
tradeStatisticsManager,
|
||||
isTakerApiUser);
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -1223,6 +1223,7 @@ setting.preferences.useAnimations=Use animations
|
|||
setting.preferences.useDarkMode=Use dark mode
|
||||
setting.preferences.sortWithNumOffers=Sort market lists with no. of offers/trades
|
||||
setting.preferences.onlyShowPaymentMethodsFromAccount=Hide non-supported payment methods
|
||||
setting.preferences.denyApiTaker=Deny takers using the API
|
||||
setting.preferences.resetAllFlags=Reset all \"Don't show again\" flags
|
||||
settings.preferences.languageChange=To apply the language change to all screens requires a restart.
|
||||
settings.preferences.supportLanguageWarning=In case of a dispute, please note that mediation is handled in {0} and arbitration in {1}.
|
||||
|
@ -2620,6 +2621,7 @@ filterWindow.disableTradeBelowVersion=Min. version required for trading
|
|||
filterWindow.add=Add filter
|
||||
filterWindow.remove=Remove filter
|
||||
filterWindow.btcFeeReceiverAddresses=BTC fee receiver addresses
|
||||
filterWindow.disableApi=Disable API
|
||||
|
||||
offerDetailsWindow.minBtcAmount=Min. BTC amount
|
||||
offerDetailsWindow.min=(min. {0})
|
||||
|
|
|
@ -236,7 +236,7 @@ public abstract class MutableOfferViewModel<M extends MutableOfferDataModel> ext
|
|||
if (DevEnv.isDevMode()) {
|
||||
UserThread.runAfter(() -> {
|
||||
amount.set("0.001");
|
||||
price.set("70000");
|
||||
price.set("210000");
|
||||
minAmount.set(amount.get());
|
||||
onFocusOutPriceAsPercentageTextField(true, false);
|
||||
applyMakerFee();
|
||||
|
|
|
@ -52,6 +52,7 @@ import bisq.core.locale.Res;
|
|||
import bisq.core.locale.TradeCurrency;
|
||||
import bisq.core.monetary.Price;
|
||||
import bisq.core.offer.Offer;
|
||||
import bisq.core.offer.OfferFilter;
|
||||
import bisq.core.offer.OfferPayload;
|
||||
import bisq.core.offer.OfferRestrictions;
|
||||
import bisq.core.payment.PaymentAccount;
|
||||
|
@ -62,6 +63,7 @@ import bisq.core.util.coin.CoinFormatter;
|
|||
|
||||
import bisq.network.p2p.NodeAddress;
|
||||
|
||||
import bisq.common.app.DevEnv;
|
||||
import bisq.common.config.Config;
|
||||
import bisq.common.util.Tuple3;
|
||||
|
||||
|
@ -617,51 +619,61 @@ public class OfferBookView extends ActivatableViewAndModel<GridPane, OfferBookVi
|
|||
}
|
||||
}
|
||||
|
||||
private void onShowInfo(Offer offer,
|
||||
boolean isPaymentAccountValidForOffer,
|
||||
boolean isInsufficientCounterpartyTradeLimit,
|
||||
boolean hasSameProtocolVersion,
|
||||
boolean isIgnored,
|
||||
boolean isOfferBanned,
|
||||
boolean isCurrencyBanned,
|
||||
boolean isPaymentMethodBanned,
|
||||
boolean isNodeAddressBanned,
|
||||
boolean requireUpdateToNewVersion,
|
||||
boolean isInsufficientTradeLimit) {
|
||||
if (!isPaymentAccountValidForOffer) {
|
||||
openPopupForMissingAccountSetup(Res.get("offerbook.warning.noMatchingAccount.headline"),
|
||||
Res.get("offerbook.warning.noMatchingAccount.msg"),
|
||||
FiatAccountsView.class,
|
||||
"navigation.account");
|
||||
} else if (isInsufficientCounterpartyTradeLimit) {
|
||||
new Popup().warning(Res.get("offerbook.warning.counterpartyTradeRestrictions")).show();
|
||||
} else if (!hasSameProtocolVersion) {
|
||||
new Popup().warning(Res.get("offerbook.warning.wrongTradeProtocol")).show();
|
||||
} else if (isIgnored) {
|
||||
new Popup().warning(Res.get("offerbook.warning.userIgnored")).show();
|
||||
} else if (isOfferBanned) {
|
||||
new Popup().warning(Res.get("offerbook.warning.offerBlocked")).show();
|
||||
} else if (isCurrencyBanned) {
|
||||
new Popup().warning(Res.get("offerbook.warning.currencyBanned")).show();
|
||||
} else if (isPaymentMethodBanned) {
|
||||
new Popup().warning(Res.get("offerbook.warning.paymentMethodBanned")).show();
|
||||
} else if (isNodeAddressBanned) {
|
||||
new Popup().warning(Res.get("offerbook.warning.nodeBlocked")).show();
|
||||
} else if (requireUpdateToNewVersion) {
|
||||
new Popup().warning(Res.get("offerbook.warning.requireUpdateToNewVersion")).show();
|
||||
} else if (isInsufficientTradeLimit) {
|
||||
final Optional<PaymentAccount> account = model.getMostMaturePaymentAccountForOffer(offer);
|
||||
if (account.isPresent()) {
|
||||
final long tradeLimit = model.accountAgeWitnessService.getMyTradeLimit(account.get(),
|
||||
offer.getCurrencyCode(), offer.getMirroredDirection());
|
||||
new Popup()
|
||||
.warning(Res.get("popup.warning.tradeLimitDueAccountAgeRestriction.buyer",
|
||||
formatter.formatCoinWithCode(Coin.valueOf(tradeLimit)),
|
||||
Res.get("offerbook.warning.newVersionAnnouncement")))
|
||||
.show();
|
||||
} else {
|
||||
log.warn("We don't found a payment account but got called the isInsufficientTradeLimit case. That must not happen.");
|
||||
}
|
||||
private void onShowInfo(Offer offer, OfferFilter.Result result) {
|
||||
switch (result) {
|
||||
case VALID:
|
||||
break;
|
||||
case API_DISABLED:
|
||||
DevEnv.logErrorAndThrowIfDevMode("We are in desktop and in the taker position " +
|
||||
"viewing offers, so it cannot be that we got that result as we are not an API user.");
|
||||
break;
|
||||
case HAS_NO_PAYMENT_ACCOUNT_VALID_FOR_OFFER:
|
||||
openPopupForMissingAccountSetup(Res.get("offerbook.warning.noMatchingAccount.headline"),
|
||||
Res.get("offerbook.warning.noMatchingAccount.msg"),
|
||||
FiatAccountsView.class,
|
||||
"navigation.account");
|
||||
break;
|
||||
case HAS_NOT_SAME_PROTOCOL_VERSION:
|
||||
new Popup().warning(Res.get("offerbook.warning.wrongTradeProtocol")).show();
|
||||
break;
|
||||
case IS_IGNORED:
|
||||
new Popup().warning(Res.get("offerbook.warning.userIgnored")).show();
|
||||
break;
|
||||
case IS_OFFER_BANNED:
|
||||
new Popup().warning(Res.get("offerbook.warning.offerBlocked")).show();
|
||||
break;
|
||||
case IS_CURRENCY_BANNED:
|
||||
new Popup().warning(Res.get("offerbook.warning.currencyBanned")).show();
|
||||
break;
|
||||
case IS_PAYMENT_METHOD_BANNED:
|
||||
new Popup().warning(Res.get("offerbook.warning.paymentMethodBanned")).show();
|
||||
break;
|
||||
case IS_NODE_ADDRESS_BANNED:
|
||||
new Popup().warning(Res.get("offerbook.warning.nodeBlocked")).show();
|
||||
break;
|
||||
case REQUIRE_UPDATE_TO_NEW_VERSION:
|
||||
new Popup().warning(Res.get("offerbook.warning.requireUpdateToNewVersion")).show();
|
||||
break;
|
||||
case IS_INSUFFICIENT_COUNTERPARTY_TRADE_LIMIT:
|
||||
new Popup().warning(Res.get("offerbook.warning.counterpartyTradeRestrictions")).show();
|
||||
break;
|
||||
case IS_MY_INSUFFICIENT_TRADE_LIMIT:
|
||||
Optional<PaymentAccount> account = model.getMostMaturePaymentAccountForOffer(offer);
|
||||
if (account.isPresent()) {
|
||||
long tradeLimit = model.accountAgeWitnessService.getMyTradeLimit(account.get(),
|
||||
offer.getCurrencyCode(), offer.getMirroredDirection());
|
||||
new Popup()
|
||||
.warning(Res.get("popup.warning.tradeLimitDueAccountAgeRestriction.buyer",
|
||||
formatter.formatCoinWithCode(Coin.valueOf(tradeLimit)),
|
||||
Res.get("offerbook.warning.newVersionAnnouncement")))
|
||||
.show();
|
||||
} else {
|
||||
DevEnv.logErrorAndThrowIfDevMode("We don't found a payment account but got called the " +
|
||||
"isInsufficientTradeLimit case.");
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1015,11 +1027,7 @@ public class OfferBookView extends ActivatableViewAndModel<GridPane, OfferBookVi
|
|||
return new TableCell<>() {
|
||||
final ImageView iconView = new ImageView();
|
||||
final AutoTooltipButton button = new AutoTooltipButton();
|
||||
boolean isTradable, isPaymentAccountValidForOffer,
|
||||
isInsufficientCounterpartyTradeLimit,
|
||||
hasSameProtocolVersion, isIgnored, isOfferBanned, isCurrencyBanned,
|
||||
isPaymentMethodBanned, isNodeAddressBanned, isMyInsufficientTradeLimit,
|
||||
requireUpdateToNewVersion;
|
||||
OfferFilter.Result canTakeOfferResult = null;
|
||||
|
||||
{
|
||||
button.setGraphic(iconView);
|
||||
|
@ -1034,37 +1042,14 @@ public class OfferBookView extends ActivatableViewAndModel<GridPane, OfferBookVi
|
|||
|
||||
TableRow<OfferBookListItem> tableRow = getTableRow();
|
||||
if (item != null && !empty) {
|
||||
final Offer offer = item.getOffer();
|
||||
Offer offer = item.getOffer();
|
||||
boolean myOffer = model.isMyOffer(offer);
|
||||
|
||||
if (tableRow != null) {
|
||||
// this code is duplicated in model.getOffersMatchingMyAccountsPredicate but as
|
||||
// we want to pass the results for displaying relevant info in popups we
|
||||
// cannot simply replace it with the predicate. If there are any changes we
|
||||
// need to maintain both.
|
||||
isPaymentAccountValidForOffer = model.isAnyPaymentAccountValidForOffer(offer);
|
||||
isInsufficientCounterpartyTradeLimit = model.isInsufficientCounterpartyTradeLimit(offer);
|
||||
hasSameProtocolVersion = model.hasSameProtocolVersion(offer);
|
||||
isIgnored = model.isIgnored(offer);
|
||||
isOfferBanned = model.isOfferBanned(offer);
|
||||
isCurrencyBanned = model.isCurrencyBanned(offer);
|
||||
isPaymentMethodBanned = model.isPaymentMethodBanned(offer);
|
||||
isNodeAddressBanned = model.isNodeAddressBanned(offer);
|
||||
requireUpdateToNewVersion = model.requireUpdateToNewVersion();
|
||||
isMyInsufficientTradeLimit = model.isMyInsufficientTradeLimit(offer);
|
||||
isTradable = isPaymentAccountValidForOffer &&
|
||||
!isInsufficientCounterpartyTradeLimit &&
|
||||
hasSameProtocolVersion &&
|
||||
!isIgnored &&
|
||||
!isOfferBanned &&
|
||||
!isCurrencyBanned &&
|
||||
!isPaymentMethodBanned &&
|
||||
!isNodeAddressBanned &&
|
||||
!requireUpdateToNewVersion &&
|
||||
!isMyInsufficientTradeLimit;
|
||||
canTakeOfferResult = model.offerFilter.canTakeOffer(offer, false);
|
||||
tableRow.setOpacity(canTakeOfferResult.isValid() || myOffer ? 1 : 0.4);
|
||||
|
||||
tableRow.setOpacity(isTradable || myOffer ? 1 : 0.4);
|
||||
|
||||
if (isTradable) {
|
||||
if (canTakeOfferResult.isValid()) {
|
||||
// set first row button as default
|
||||
button.setDefaultButton(getIndex() == 0);
|
||||
tableRow.setOnMousePressed(null);
|
||||
|
@ -1073,17 +1058,7 @@ public class OfferBookView extends ActivatableViewAndModel<GridPane, OfferBookVi
|
|||
tableRow.setOnMousePressed(e -> {
|
||||
// ugly hack to get the icon clickable when deactivated
|
||||
if (!(e.getTarget() instanceof ImageView || e.getTarget() instanceof Canvas))
|
||||
onShowInfo(offer,
|
||||
isPaymentAccountValidForOffer,
|
||||
isInsufficientCounterpartyTradeLimit,
|
||||
hasSameProtocolVersion,
|
||||
isIgnored,
|
||||
isOfferBanned,
|
||||
isCurrencyBanned,
|
||||
isPaymentMethodBanned,
|
||||
isNodeAddressBanned,
|
||||
requireUpdateToNewVersion,
|
||||
isMyInsufficientTradeLimit);
|
||||
onShowInfo(offer, canTakeOfferResult);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -1113,18 +1088,15 @@ public class OfferBookView extends ActivatableViewAndModel<GridPane, OfferBookVi
|
|||
button.setOnAction(e -> onTakeOffer(offer));
|
||||
}
|
||||
|
||||
if (!myOffer && !isTradable)
|
||||
button.setOnAction(e -> onShowInfo(offer,
|
||||
isPaymentAccountValidForOffer,
|
||||
isInsufficientCounterpartyTradeLimit,
|
||||
hasSameProtocolVersion,
|
||||
isIgnored,
|
||||
isOfferBanned,
|
||||
isCurrencyBanned,
|
||||
isPaymentMethodBanned,
|
||||
isNodeAddressBanned,
|
||||
requireUpdateToNewVersion,
|
||||
isMyInsufficientTradeLimit));
|
||||
if (!myOffer) {
|
||||
if (canTakeOfferResult == null) {
|
||||
canTakeOfferResult = model.offerFilter.canTakeOffer(offer, false);
|
||||
}
|
||||
|
||||
if (!canTakeOfferResult.isValid()) {
|
||||
button.setOnAction(e -> onShowInfo(offer, canTakeOfferResult));
|
||||
}
|
||||
}
|
||||
|
||||
button.updateText(title);
|
||||
setPadding(new Insets(0, 15, 0, 0));
|
||||
|
|
|
@ -28,7 +28,6 @@ import bisq.desktop.util.GUIUtil;
|
|||
|
||||
import bisq.core.account.witness.AccountAgeWitnessService;
|
||||
import bisq.core.btc.setup.WalletsSetup;
|
||||
import bisq.core.filter.FilterManager;
|
||||
import bisq.core.locale.BankUtil;
|
||||
import bisq.core.locale.CountryUtil;
|
||||
import bisq.core.locale.CryptoCurrency;
|
||||
|
@ -39,6 +38,7 @@ import bisq.core.locale.TradeCurrency;
|
|||
import bisq.core.monetary.Price;
|
||||
import bisq.core.monetary.Volume;
|
||||
import bisq.core.offer.Offer;
|
||||
import bisq.core.offer.OfferFilter;
|
||||
import bisq.core.offer.OfferPayload;
|
||||
import bisq.core.offer.OpenOfferManager;
|
||||
import bisq.core.payment.PaymentAccount;
|
||||
|
@ -56,7 +56,6 @@ import bisq.core.util.coin.CoinFormatter;
|
|||
import bisq.network.p2p.NodeAddress;
|
||||
import bisq.network.p2p.P2PService;
|
||||
|
||||
import bisq.common.app.Version;
|
||||
import bisq.common.handlers.ErrorMessageHandler;
|
||||
import bisq.common.handlers.ResultHandler;
|
||||
|
||||
|
@ -78,14 +77,12 @@ import javafx.beans.property.StringProperty;
|
|||
import javafx.collections.FXCollections;
|
||||
import javafx.collections.ListChangeListener;
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.collections.SetChangeListener;
|
||||
import javafx.collections.transformation.FilteredList;
|
||||
import javafx.collections.transformation.SortedList;
|
||||
|
||||
import java.text.DecimalFormat;
|
||||
|
||||
import java.util.Comparator;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
|
@ -105,10 +102,10 @@ class OfferBookViewModel extends ActivatableViewModel {
|
|||
private final P2PService p2PService;
|
||||
final PriceFeedService priceFeedService;
|
||||
private final ClosedTradableManager closedTradableManager;
|
||||
private final FilterManager filterManager;
|
||||
final AccountAgeWitnessService accountAgeWitnessService;
|
||||
private final Navigation navigation;
|
||||
private final PriceUtil priceUtil;
|
||||
final OfferFilter offerFilter;
|
||||
private final CoinFormatter btcFormatter;
|
||||
private final BsqFormatter bsqFormatter;
|
||||
|
||||
|
@ -136,8 +133,6 @@ class OfferBookViewModel extends ActivatableViewModel {
|
|||
final IntegerProperty maxPlacesForMarketPriceMargin = new SimpleIntegerProperty();
|
||||
boolean showAllPaymentMethods = true;
|
||||
boolean useOffersMatchingMyAccountsFilter;
|
||||
private final Map<String, Boolean> myInsufficientTradeLimitCache = new HashMap<>();
|
||||
private final Map<String, Boolean> insufficientCounterpartyTradeLimitCache = new HashMap<>();
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
@ -153,10 +148,10 @@ class OfferBookViewModel extends ActivatableViewModel {
|
|||
P2PService p2PService,
|
||||
PriceFeedService priceFeedService,
|
||||
ClosedTradableManager closedTradableManager,
|
||||
FilterManager filterManager,
|
||||
AccountAgeWitnessService accountAgeWitnessService,
|
||||
Navigation navigation,
|
||||
PriceUtil priceUtil,
|
||||
OfferFilter offerFilter,
|
||||
@Named(FormattingUtils.BTC_FORMATTER_KEY) CoinFormatter btcFormatter,
|
||||
BsqFormatter bsqFormatter) {
|
||||
super();
|
||||
|
@ -169,10 +164,10 @@ class OfferBookViewModel extends ActivatableViewModel {
|
|||
this.p2PService = p2PService;
|
||||
this.priceFeedService = priceFeedService;
|
||||
this.closedTradableManager = closedTradableManager;
|
||||
this.filterManager = filterManager;
|
||||
this.accountAgeWitnessService = accountAgeWitnessService;
|
||||
this.navigation = navigation;
|
||||
this.priceUtil = priceUtil;
|
||||
this.offerFilter = offerFilter;
|
||||
this.btcFormatter = btcFormatter;
|
||||
this.bsqFormatter = bsqFormatter;
|
||||
|
||||
|
@ -213,12 +208,6 @@ class OfferBookViewModel extends ActivatableViewModel {
|
|||
|
||||
highestMarketPriceMarginOffer.ifPresent(offerBookListItem -> maxPlacesForMarketPriceMargin.set(formatMarketPriceMargin(offerBookListItem.getOffer(), false).length()));
|
||||
};
|
||||
|
||||
// If our accounts have changed we reset our myInsufficientTradeLimitCache as it depends on account data
|
||||
if (user != null) {
|
||||
user.getPaymentAccountsAsObservable().addListener((SetChangeListener<PaymentAccount>) c ->
|
||||
myInsufficientTradeLimitCache.clear());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -568,11 +557,6 @@ class OfferBookViewModel extends ActivatableViewModel {
|
|||
// Checks
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
boolean isAnyPaymentAccountValidForOffer(Offer offer) {
|
||||
return user.getPaymentAccounts() != null &&
|
||||
PaymentAccountUtil.isAnyTakerPaymentAccountValidForOffer(offer, user.getPaymentAccounts());
|
||||
}
|
||||
|
||||
boolean hasPaymentAccountForCurrency() {
|
||||
return (showAllTradeCurrenciesProperty.get() &&
|
||||
user.getPaymentAccounts() != null &&
|
||||
|
@ -615,98 +599,11 @@ class OfferBookViewModel extends ActivatableViewModel {
|
|||
// This code duplicates code in the view at the button column. We need there the different results for
|
||||
// display in popups so we cannot replace that with the predicate. Any change need to be applied in both
|
||||
// places.
|
||||
return offerBookListItem -> {
|
||||
Offer offer = offerBookListItem.getOffer();
|
||||
boolean isPaymentAccountValidForOffer = isAnyPaymentAccountValidForOffer(offer);
|
||||
boolean isInsufficientCounterpartyTradeLimit = isInsufficientCounterpartyTradeLimit(offer);
|
||||
boolean hasSameProtocolVersion = hasSameProtocolVersion(offer);
|
||||
boolean isIgnored = isIgnored(offer);
|
||||
boolean isOfferBanned = isOfferBanned(offer);
|
||||
boolean isCurrencyBanned = isCurrencyBanned(offer);
|
||||
boolean isPaymentMethodBanned = isPaymentMethodBanned(offer);
|
||||
boolean isNodeAddressBanned = isNodeAddressBanned(offer);
|
||||
boolean requireUpdateToNewVersion = requireUpdateToNewVersion();
|
||||
boolean isMyInsufficientTradeLimit = isMyInsufficientTradeLimit(offer);
|
||||
boolean isTradable = isPaymentAccountValidForOffer &&
|
||||
!isInsufficientCounterpartyTradeLimit &&
|
||||
hasSameProtocolVersion &&
|
||||
!isIgnored &&
|
||||
!isOfferBanned &&
|
||||
!isCurrencyBanned &&
|
||||
!isPaymentMethodBanned &&
|
||||
!isNodeAddressBanned &&
|
||||
!requireUpdateToNewVersion &&
|
||||
!isMyInsufficientTradeLimit;
|
||||
return isTradable;
|
||||
};
|
||||
}
|
||||
|
||||
boolean isIgnored(Offer offer) {
|
||||
return preferences.getIgnoreTradersList().stream()
|
||||
.anyMatch(i -> i.equals(offer.getMakerNodeAddress().getFullAddress()));
|
||||
return offerBookListItem -> offerFilter.canTakeOffer(offerBookListItem.getOffer(), false).isValid();
|
||||
}
|
||||
|
||||
boolean isOfferBanned(Offer offer) {
|
||||
return filterManager.isOfferIdBanned(offer.getId());
|
||||
}
|
||||
|
||||
boolean isCurrencyBanned(Offer offer) {
|
||||
return filterManager.isCurrencyBanned(offer.getCurrencyCode());
|
||||
}
|
||||
|
||||
boolean isPaymentMethodBanned(Offer offer) {
|
||||
return filterManager.isPaymentMethodBanned(offer.getPaymentMethod());
|
||||
}
|
||||
|
||||
boolean isNodeAddressBanned(Offer offer) {
|
||||
return filterManager.isNodeAddressBanned(offer.getMakerNodeAddress());
|
||||
}
|
||||
|
||||
boolean requireUpdateToNewVersion() {
|
||||
return filterManager.requireUpdateToNewVersionForTrading();
|
||||
}
|
||||
|
||||
// This call is a bit expensive so we cache results
|
||||
boolean isInsufficientCounterpartyTradeLimit(Offer offer) {
|
||||
String offerId = offer.getId();
|
||||
if (insufficientCounterpartyTradeLimitCache.containsKey(offerId)) {
|
||||
return insufficientCounterpartyTradeLimitCache.get(offerId);
|
||||
}
|
||||
|
||||
boolean result = CurrencyUtil.isFiatCurrency(offer.getCurrencyCode()) &&
|
||||
!accountAgeWitnessService.verifyPeersTradeAmount(offer, offer.getAmount(),
|
||||
errorMessage -> {
|
||||
});
|
||||
insufficientCounterpartyTradeLimitCache.put(offerId, result);
|
||||
return result;
|
||||
}
|
||||
|
||||
// This call is a bit expensive so we cache results
|
||||
boolean isMyInsufficientTradeLimit(Offer offer) {
|
||||
String offerId = offer.getId();
|
||||
if (myInsufficientTradeLimitCache.containsKey(offerId)) {
|
||||
return myInsufficientTradeLimitCache.get(offerId);
|
||||
}
|
||||
|
||||
Optional<PaymentAccount> accountOptional = getMostMaturePaymentAccountForOffer(offer);
|
||||
long myTradeLimit = accountOptional
|
||||
.map(paymentAccount -> accountAgeWitnessService.getMyTradeLimit(paymentAccount,
|
||||
offer.getCurrencyCode(), offer.getMirroredDirection()))
|
||||
.orElse(0L);
|
||||
long offerMinAmount = offer.getMinAmount().value;
|
||||
log.debug("isInsufficientTradeLimit accountOptional={}, myTradeLimit={}, offerMinAmount={}, ",
|
||||
accountOptional.isPresent() ? accountOptional.get().getAccountName() : "null",
|
||||
Coin.valueOf(myTradeLimit).toFriendlyString(),
|
||||
Coin.valueOf(offerMinAmount).toFriendlyString());
|
||||
boolean result = CurrencyUtil.isFiatCurrency(offer.getCurrencyCode()) &&
|
||||
accountOptional.isPresent() &&
|
||||
myTradeLimit < offerMinAmount;
|
||||
myInsufficientTradeLimitCache.put(offerId, result);
|
||||
return result;
|
||||
}
|
||||
|
||||
boolean hasSameProtocolVersion(Offer offer) {
|
||||
return offer.getProtocolVersion() == Version.TRADE_PROTOCOL_VERSION;
|
||||
return offerFilter.isOfferBanned(offer);
|
||||
}
|
||||
|
||||
private boolean isShowAllEntry(String id) {
|
||||
|
|
|
@ -171,6 +171,7 @@ class TakeOfferDataModel extends OfferDataModel {
|
|||
|
||||
if (canTakeOffer()) {
|
||||
tradeManager.checkOfferAvailability(offer,
|
||||
false,
|
||||
() -> {
|
||||
},
|
||||
errorMessage -> new Popup().warning(errorMessage).show());
|
||||
|
@ -319,7 +320,8 @@ class TakeOfferDataModel extends OfferDataModel {
|
|||
offer,
|
||||
paymentAccount.getId(),
|
||||
useSavingsWallet,
|
||||
tradeResultHandler::handleResult,
|
||||
false,
|
||||
tradeResultHandler,
|
||||
errorMessage -> {
|
||||
log.warn(errorMessage);
|
||||
new Popup().warning(errorMessage).show();
|
||||
|
|
|
@ -239,7 +239,7 @@ public class OfferBookViewModelTest {
|
|||
when(offerBook.getOfferBookListItems()).thenReturn(offerBookListItems);
|
||||
|
||||
final OfferBookViewModel model = new OfferBookViewModel(null, null, offerBook, empty, null, null, null,
|
||||
null, null, null, null, getPriceUtil(), coinFormatter, new BsqFormatter());
|
||||
null, null, null, getPriceUtil(), null, coinFormatter, new BsqFormatter());
|
||||
assertEquals(0, model.maxPlacesForAmount.intValue());
|
||||
}
|
||||
|
||||
|
@ -253,7 +253,7 @@ public class OfferBookViewModelTest {
|
|||
when(offerBook.getOfferBookListItems()).thenReturn(offerBookListItems);
|
||||
|
||||
final OfferBookViewModel model = new OfferBookViewModel(null, openOfferManager, offerBook, empty, null, null, null,
|
||||
null, null, null, null, getPriceUtil(), coinFormatter, new BsqFormatter());
|
||||
null, null, null, getPriceUtil(), null, coinFormatter, new BsqFormatter());
|
||||
model.activate();
|
||||
|
||||
assertEquals(6, model.maxPlacesForAmount.intValue());
|
||||
|
@ -271,7 +271,7 @@ public class OfferBookViewModelTest {
|
|||
when(offerBook.getOfferBookListItems()).thenReturn(offerBookListItems);
|
||||
|
||||
final OfferBookViewModel model = new OfferBookViewModel(null, openOfferManager, offerBook, empty, null, null, null,
|
||||
null, null, null, null, getPriceUtil(), coinFormatter, new BsqFormatter());
|
||||
null, null, null, getPriceUtil(), null, coinFormatter, new BsqFormatter());
|
||||
model.activate();
|
||||
|
||||
assertEquals(15, model.maxPlacesForAmount.intValue());
|
||||
|
@ -290,7 +290,7 @@ public class OfferBookViewModelTest {
|
|||
when(offerBook.getOfferBookListItems()).thenReturn(offerBookListItems);
|
||||
|
||||
final OfferBookViewModel model = new OfferBookViewModel(null, null, offerBook, empty, null, null, null,
|
||||
null, null, null, null, getPriceUtil(), coinFormatter, new BsqFormatter());
|
||||
null, null, null, getPriceUtil(), null, coinFormatter, new BsqFormatter());
|
||||
assertEquals(0, model.maxPlacesForVolume.intValue());
|
||||
}
|
||||
|
||||
|
@ -304,7 +304,7 @@ public class OfferBookViewModelTest {
|
|||
when(offerBook.getOfferBookListItems()).thenReturn(offerBookListItems);
|
||||
|
||||
final OfferBookViewModel model = new OfferBookViewModel(null, openOfferManager, offerBook, empty, null, null, null,
|
||||
null, null, null, null, getPriceUtil(), coinFormatter, new BsqFormatter());
|
||||
null, null, null, getPriceUtil(), null, coinFormatter, new BsqFormatter());
|
||||
model.activate();
|
||||
|
||||
assertEquals(5, model.maxPlacesForVolume.intValue());
|
||||
|
@ -322,7 +322,7 @@ public class OfferBookViewModelTest {
|
|||
when(offerBook.getOfferBookListItems()).thenReturn(offerBookListItems);
|
||||
|
||||
final OfferBookViewModel model = new OfferBookViewModel(null, openOfferManager, offerBook, empty, null, null, null,
|
||||
null, null, null, null, getPriceUtil(), coinFormatter, new BsqFormatter());
|
||||
null, null, null, getPriceUtil(), null, coinFormatter, new BsqFormatter());
|
||||
model.activate();
|
||||
|
||||
assertEquals(9, model.maxPlacesForVolume.intValue());
|
||||
|
@ -341,7 +341,7 @@ public class OfferBookViewModelTest {
|
|||
when(offerBook.getOfferBookListItems()).thenReturn(offerBookListItems);
|
||||
|
||||
final OfferBookViewModel model = new OfferBookViewModel(null, null, offerBook, empty, null, null, null,
|
||||
null, null, null, null, getPriceUtil(), coinFormatter, new BsqFormatter());
|
||||
null, null, null, getPriceUtil(), null, coinFormatter, new BsqFormatter());
|
||||
assertEquals(0, model.maxPlacesForPrice.intValue());
|
||||
}
|
||||
|
||||
|
@ -355,7 +355,7 @@ public class OfferBookViewModelTest {
|
|||
when(offerBook.getOfferBookListItems()).thenReturn(offerBookListItems);
|
||||
|
||||
final OfferBookViewModel model = new OfferBookViewModel(null, openOfferManager, offerBook, empty, null, null, null,
|
||||
null, null, null, null, getPriceUtil(), coinFormatter, new BsqFormatter());
|
||||
null, null, null, getPriceUtil(), null, coinFormatter, new BsqFormatter());
|
||||
model.activate();
|
||||
|
||||
assertEquals(7, model.maxPlacesForPrice.intValue());
|
||||
|
@ -373,7 +373,7 @@ public class OfferBookViewModelTest {
|
|||
when(offerBook.getOfferBookListItems()).thenReturn(offerBookListItems);
|
||||
|
||||
final OfferBookViewModel model = new OfferBookViewModel(null, null, offerBook, empty, null, null, null,
|
||||
null, null, null, null, getPriceUtil(), coinFormatter, new BsqFormatter());
|
||||
null, null, null, getPriceUtil(), null, coinFormatter, new BsqFormatter());
|
||||
assertEquals(0, model.maxPlacesForMarketPriceMargin.intValue());
|
||||
}
|
||||
|
||||
|
@ -401,7 +401,7 @@ public class OfferBookViewModelTest {
|
|||
offerBookListItems.addAll(item1, item2);
|
||||
|
||||
final OfferBookViewModel model = new OfferBookViewModel(null, openOfferManager, offerBook, empty, null, null, priceFeedService,
|
||||
null, null, null, null, getPriceUtil(), coinFormatter, new BsqFormatter());
|
||||
null, null, null, getPriceUtil(), null, coinFormatter, new BsqFormatter());
|
||||
model.activate();
|
||||
|
||||
assertEquals(8, model.maxPlacesForMarketPriceMargin.intValue()); //" (1.97%)"
|
||||
|
@ -422,7 +422,7 @@ public class OfferBookViewModelTest {
|
|||
when(priceFeedService.getMarketPrice(anyString())).thenReturn(new MarketPrice("USD", 12684.0450, Instant.now().getEpochSecond(), true));
|
||||
|
||||
final OfferBookViewModel model = new OfferBookViewModel(null, openOfferManager, offerBook, empty, null, null, null,
|
||||
null, null, null, null, getPriceUtil(), coinFormatter, new BsqFormatter());
|
||||
null, null, null, getPriceUtil(), null, coinFormatter, new BsqFormatter());
|
||||
|
||||
final OfferBookListItem item = make(btcBuyItem.but(
|
||||
with(useMarketBasedPrice, true),
|
||||
|
|
|
@ -158,6 +158,7 @@ message OfferAvailabilityRequest {
|
|||
int64 takers_trade_price = 3;
|
||||
repeated int32 supported_capabilities = 4;
|
||||
string uid = 5;
|
||||
bool is_taker_api_user = 6;
|
||||
}
|
||||
|
||||
message OfferAvailabilityResponse {
|
||||
|
@ -914,6 +915,8 @@ enum AvailabilityResult {
|
|||
USER_IGNORED = 8;
|
||||
MISSING_MANDATORY_CAPABILITY = 9;
|
||||
NO_REFUND_AGENTS = 10;
|
||||
UNCONF_TX_LIMIT_HIT = 11;
|
||||
MAKER_DENIED_API_USER = 12;
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
|
Loading…
Add table
Reference in a new issue