Skip to content

Commit

Permalink
Add server/core editOffer, adjust getMyOffer(s) impls
Browse files Browse the repository at this point in the history
- Add editOffer to GrpcOffersService, CoreApi, CoreOffersService.

- Set editOffer call rate meter to 1 / minute.

- Use new EditOfferValidator to verify editOffer params OK.

- Adust getMyOffer(s) rpc impl and OfferInfo model to use OpenOffer
  for accessing activation state and trigger price.
  • Loading branch information
ghubstan committed Jun 13, 2021
1 parent 1daf471 commit 2b8b53b
Show file tree
Hide file tree
Showing 5 changed files with 302 additions and 84 deletions.
43 changes: 16 additions & 27 deletions core/src/main/java/bisq/core/api/CoreApi.java
Expand Up @@ -21,9 +21,7 @@
import bisq.core.api.model.BalancesInfo;
import bisq.core.api.model.TxFeeRateInfo;
import bisq.core.btc.wallet.TxBroadcaster;
import bisq.core.monetary.Price;
import bisq.core.offer.Offer;
import bisq.core.offer.OfferPayload;
import bisq.core.offer.OpenOffer;
import bisq.core.payment.PaymentAccount;
import bisq.core.payment.payload.PaymentMethod;
Expand All @@ -36,7 +34,6 @@
import bisq.common.handlers.ErrorMessageHandler;
import bisq.common.handlers.ResultHandler;

import org.bitcoinj.core.Coin;
import org.bitcoinj.core.Transaction;

import javax.inject.Inject;
Expand All @@ -52,6 +49,8 @@
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;

import static bisq.proto.grpc.EditOfferRequest.EditType;

/**
* Provides high level interface to functionality of core Bisq features.
* E.g. useful for different APIs to access data of different domains of Bisq.
Expand Down Expand Up @@ -122,22 +121,18 @@ public Offer getOffer(String id) {
return coreOffersService.getOffer(id);
}

public Offer getMyOffer(String id) {
public OpenOffer getMyOffer(String id) {
return coreOffersService.getMyOffer(id);
}

public List<Offer> getOffers(String direction, String currencyCode) {
return coreOffersService.getOffers(direction, currencyCode);
}

public List<Offer> getMyOffers(String direction, String currencyCode) {
public List<OpenOffer> getMyOffers(String direction, String currencyCode) {
return coreOffersService.getMyOffers(direction, currencyCode);
}

public OpenOffer getMyOpenOffer(String id) {
return coreOffersService.getMyOpenOffer(id);
}

public void createAnPlaceOffer(String currencyCode,
String directionAsString,
String priceAsString,
Expand All @@ -164,26 +159,20 @@ public void createAnPlaceOffer(String currencyCode,
resultHandler);
}

public Offer editOffer(String offerId,
String currencyCode,
OfferPayload.Direction direction,
Price price,
boolean useMarketBasedPrice,
double marketPriceMargin,
Coin amount,
Coin minAmount,
double buyerSecurityDeposit,
PaymentAccount paymentAccount) {
return coreOffersService.editOffer(offerId,
currencyCode,
direction,
price,
public void editOffer(String offerId,
String priceAsString,
boolean useMarketBasedPrice,
double marketPriceMargin,
long triggerPrice,
int enable,
EditType editType) {
coreOffersService.editOffer(offerId,
priceAsString,
useMarketBasedPrice,
marketPriceMargin,
amount,
minAmount,
buyerSecurityDeposit,
paymentAccount);
triggerPrice,
enable,
editType);
}

public void cancelOffer(String id) {
Expand Down
156 changes: 109 additions & 47 deletions core/src/main/java/bisq/core/api/CoreOffersService.java
Expand Up @@ -20,13 +20,16 @@
import bisq.core.monetary.Altcoin;
import bisq.core.monetary.Price;
import bisq.core.offer.CreateOfferService;
import bisq.core.offer.MutableOfferPayloadFields;
import bisq.core.offer.Offer;
import bisq.core.offer.OfferBookService;
import bisq.core.offer.OfferFilter;
import bisq.core.offer.OfferPayload;
import bisq.core.offer.OfferUtil;
import bisq.core.offer.OpenOffer;
import bisq.core.offer.OpenOfferManager;
import bisq.core.payment.PaymentAccount;
import bisq.core.provider.price.PriceFeedService;
import bisq.core.user.User;

import bisq.common.crypto.KeyRing;
Expand Down Expand Up @@ -54,16 +57,22 @@
import static bisq.core.locale.CurrencyUtil.isCryptoCurrency;
import static bisq.core.offer.OfferPayload.Direction;
import static bisq.core.offer.OfferPayload.Direction.BUY;
import static bisq.core.offer.OpenOffer.State.AVAILABLE;
import static bisq.core.offer.OpenOffer.State.DEACTIVATED;
import static bisq.core.payment.PaymentAccountUtil.isPaymentAccountValidForOffer;
import static bisq.proto.grpc.EditOfferRequest.EditType;
import static java.lang.String.format;
import static java.util.Comparator.comparing;

@Singleton
@Slf4j
class CoreOffersService {

private final Supplier<Comparator<Offer>> priceComparator = () -> comparing(Offer::getPrice);
private final Supplier<Comparator<Offer>> reversePriceComparator = () -> comparing(Offer::getPrice).reversed();
private final Supplier<Comparator<Offer>> priceComparator = () ->
comparing(Offer::getPrice);

private final Supplier<Comparator<OpenOffer>> openOfferPriceComparator = () ->
comparing(openOffer -> openOffer.getOffer().getPrice());

private final CoreContext coreContext;
private final KeyRing keyRing;
Expand All @@ -76,6 +85,7 @@ class CoreOffersService {
private final OfferFilter offerFilter;
private final OpenOfferManager openOfferManager;
private final OfferUtil offerUtil;
private final PriceFeedService priceFeedService;
private final User user;

@Inject
Expand All @@ -87,6 +97,7 @@ public CoreOffersService(CoreContext coreContext,
OfferFilter offerFilter,
OpenOfferManager openOfferManager,
OfferUtil offerUtil,
PriceFeedService priceFeedService,
User user) {
this.coreContext = coreContext;
this.keyRing = keyRing;
Expand All @@ -96,6 +107,7 @@ public CoreOffersService(CoreContext coreContext,
this.offerFilter = offerFilter;
this.openOfferManager = openOfferManager;
this.offerUtil = offerUtil;
this.priceFeedService = priceFeedService;
this.user = user;
}

Expand All @@ -108,10 +120,10 @@ Offer getOffer(String id) {
new IllegalStateException(format("offer with id '%s' not found", id)));
}

Offer getMyOffer(String id) {
return offerBookService.getOffers().stream()
OpenOffer getMyOffer(String id) {
return openOfferManager.getObservableList().stream()
.filter(o -> o.getId().equals(id))
.filter(o -> o.isMyOffer(keyRing))
.filter(o -> o.getOffer().isMyOffer(keyRing))
.findAny().orElseThrow(() ->
new IllegalStateException(format("offer with id '%s' not found", id)));
}
Expand All @@ -125,19 +137,19 @@ List<Offer> getOffers(String direction, String currencyCode) {
.collect(Collectors.toList());
}

List<Offer> getMyOffers(String direction, String currencyCode) {
return offerBookService.getOffers().stream()
.filter(o -> o.isMyOffer(keyRing))
.filter(o -> offerMatchesDirectionAndCurrency(o, direction, currencyCode))
.sorted(priceComparator(direction))
List<OpenOffer> getMyOffers(String direction, String currencyCode) {
return openOfferManager.getObservableList().stream()
.filter(o -> o.getOffer().isMyOffer(keyRing))
.filter(o -> offerMatchesDirectionAndCurrency(o.getOffer(), direction, currencyCode))
.sorted(openOfferPriceComparator(direction))
.collect(Collectors.toList());
}

OpenOffer getMyOpenOffer(String id) {
return openOfferManager.getOpenOfferById(id)
.filter(open -> open.getOffer().isMyOffer(keyRing))
.orElseThrow(() ->
new IllegalStateException(format("openoffer with id '%s' not found", id)));
new IllegalStateException(format("offer with id '%s' not found", id)));
}

// Create and place new offer.
Expand Down Expand Up @@ -193,47 +205,56 @@ void createAndPlaceOffer(String currencyCode,
}

// Edit a placed offer.
Offer editOffer(String offerId,
String currencyCode,
Direction direction,
Price price,
boolean useMarketBasedPrice,
double marketPriceMargin,
Coin amount,
Coin minAmount,
double buyerSecurityDeposit,
PaymentAccount paymentAccount) {
Coin useDefaultTxFee = Coin.ZERO;
return createOfferService.createAndGetOffer(offerId,
direction,
currencyCode.toUpperCase(),
amount,
minAmount,
price,
useDefaultTxFee,
useMarketBasedPrice,
exactMultiply(marketPriceMargin, 0.01),
buyerSecurityDeposit,
paymentAccount);
}

void cancelOffer(String id) {
Offer offer = getMyOffer(id);
openOfferManager.removeOffer(offer,
void editOffer(String offerId,
String editedPriceAsString,
boolean editedUseMarketBasedPrice,
double editedMarketPriceMargin,
long editedTriggerPrice,
int editedEnable,
EditType editType) {
OpenOffer openOffer = getMyOpenOffer(offerId);
new EditOfferValidator(openOffer,
editedPriceAsString,
editedUseMarketBasedPrice,
editedMarketPriceMargin,
editedTriggerPrice,
editType).validate();
OfferPayload editedPayload = getMergedOfferPayload(openOffer, editedPriceAsString,
editedUseMarketBasedPrice,
editedMarketPriceMargin);
Offer editedOffer = new Offer(editedPayload);
priceFeedService.setCurrencyCode(openOffer.getOffer().getOfferPayload().getCurrencyCode());
editedOffer.setPriceFeedService(priceFeedService);
editedOffer.setState(Offer.State.AVAILABLE);
openOfferManager.editOpenOfferStart(openOffer,
() -> {
log.info("EditOpenOfferStart: offer {}", openOffer.getId());
},
errorMessage -> {
throw new IllegalStateException(errorMessage);
log.error(errorMessage);
});
// Client sent (sint32) newEnable, not a bool (with default=false).
// If newEnable = -1, do not change activation state
// If newEnable = 0, set state = AVAILABLE
// If newEnable = 1, set state = DEACTIVATED
OpenOffer.State newOfferState = editedEnable < 0
? openOffer.getState()
: editedEnable > 0 ? AVAILABLE : DEACTIVATED;
openOfferManager.editOpenOfferPublish(editedOffer,
editedTriggerPrice,
newOfferState,
() -> {
log.info("EditOpenOfferPublish: offer {}", openOffer.getId());
},
log::error);
}

private void verifyPaymentAccountIsValidForNewOffer(Offer offer, PaymentAccount paymentAccount) {
if (!isPaymentAccountValidForOffer(offer, paymentAccount)) {
String error = format("cannot create %s offer with payment account %s",
offer.getOfferPayload().getCounterCurrencyCode(),
paymentAccount.getId());
throw new IllegalStateException(error);
}
void cancelOffer(String id) {
OpenOffer openOffer = getMyOffer(id);
openOfferManager.removeOffer(openOffer.getOffer(),
() -> {
},
log::error);
}

private void placeOffer(Offer offer,
Expand All @@ -252,6 +273,39 @@ private void placeOffer(Offer offer,
throw new IllegalStateException(offer.getErrorMessage());
}

private OfferPayload getMergedOfferPayload(OpenOffer openOffer,
String editedPriceAsString,
boolean editedUseMarketBasedPrice,
double editedMarketPriceMargin) {
// API supports editing price, marketPriceMargin, useMarketBasedPrice payload
// fields. API does not support editing payment acct or currency code fields.
Offer offer = openOffer.getOffer();
String currencyCode = offer.getOfferPayload().getCurrencyCode();
Price editedPrice = Price.valueOf(currencyCode, priceStringToLong(editedPriceAsString, currencyCode));
MutableOfferPayloadFields mutableOfferPayloadFields = new MutableOfferPayloadFields(
editedPrice.getValue(),
exactMultiply(editedMarketPriceMargin, 0.01),
editedUseMarketBasedPrice,
offer.getOfferPayload().getBaseCurrencyCode(),
offer.getOfferPayload().getCounterCurrencyCode(),
offer.getPaymentMethod().getId(),
offer.getMakerPaymentAccountId(),
offer.getOfferPayload().getCountryCode(),
offer.getOfferPayload().getAcceptedCountryCodes(),
offer.getOfferPayload().getBankId(),
offer.getOfferPayload().getAcceptedBankIds());
return offerUtil.getMergedOfferPayload(openOffer, mutableOfferPayloadFields);
}

private void verifyPaymentAccountIsValidForNewOffer(Offer offer, PaymentAccount paymentAccount) {
if (!isPaymentAccountValidForOffer(offer, paymentAccount)) {
String error = format("cannot create %s offer with payment account %s",
offer.getOfferPayload().getCounterCurrencyCode(),
paymentAccount.getId());
throw new IllegalStateException(error);
}
}

private boolean offerMatchesDirectionAndCurrency(Offer offer,
String direction,
String currencyCode) {
Expand All @@ -261,11 +315,19 @@ private boolean offerMatchesDirectionAndCurrency(Offer offer,
return offerOfWantedDirection && offerInWantedCurrency;
}

private Comparator<OpenOffer> openOfferPriceComparator(String direction) {
// A buyer probably wants to see sell orders in price ascending order.
// A seller probably wants to see buy orders in price descending order.
return direction.equalsIgnoreCase(BUY.name())
? openOfferPriceComparator.get().reversed()
: openOfferPriceComparator.get();
}

private Comparator<Offer> priceComparator(String direction) {
// A buyer probably wants to see sell orders in price ascending order.
// A seller probably wants to see buy orders in price descending order.
return direction.equalsIgnoreCase(BUY.name())
? reversePriceComparator.get()
? priceComparator.get().reversed()
: priceComparator.get();
}

Expand Down

0 comments on commit 2b8b53b

Please sign in to comment.