mirror of
https://github.com/bisq-network/bisq.git
synced 2025-01-18 21:35:03 +01:00
Various refactorings and changes
Signed-off-by: HenrikJannsen <boilingfrog@gmx.com>
This commit is contained in:
parent
cadf2073cf
commit
992854c9b9
@ -124,7 +124,8 @@ public class Balances {
|
||||
private void updateLockedBalance() {
|
||||
Stream<Trade> lockedTrades = Stream.concat(closedTradableManager.getTradesStreamWithFundsLockedIn(), failedTradesManager.getTradesStreamWithFundsLockedIn());
|
||||
lockedTrades = Stream.concat(lockedTrades, tradeManager.getTradesStreamWithFundsLockedIn());
|
||||
long sum = lockedTrades.map(trade -> btcWalletService.getAddressEntry(trade.getId(), AddressEntry.Context.MULTI_SIG)
|
||||
long sum = lockedTrades.map(trade -> btcWalletService.getAddressEntry(trade.getId(),
|
||||
AddressEntry.Context.MULTI_SIG)
|
||||
.orElse(null))
|
||||
.filter(Objects::nonNull)
|
||||
.mapToLong(AddressEntry::getCoinLockedInMultiSig)
|
||||
|
@ -630,18 +630,22 @@ public class BtcWalletService extends WalletService {
|
||||
.findAny();
|
||||
}
|
||||
|
||||
// when a new offer needs to share the reserved amount info from parent offer's address entry
|
||||
public AddressEntry getOrCreateAddressEntry(AddressEntry orgAddressEntry, String offerId) {
|
||||
// For offers with shared maker fee we create a new address entry with from the source entry
|
||||
// and set the new offerId.
|
||||
public AddressEntry getOrCloneAddressEntryWithOfferId(AddressEntry sourceAddressEntry, String offerId) {
|
||||
Optional<AddressEntry> addressEntry = getAddressEntryListAsImmutableList().stream()
|
||||
.filter(e -> offerId.equals(e.getOfferId()))
|
||||
.filter(e -> orgAddressEntry.getContext() == e.getContext())
|
||||
.filter(entry -> offerId.equals(entry.getOfferId()))
|
||||
.filter(entry -> sourceAddressEntry.getContext() == entry.getContext())
|
||||
.findAny();
|
||||
if (addressEntry.isPresent()) {
|
||||
return addressEntry.get();
|
||||
} else {
|
||||
AddressEntry newEntry = new AddressEntry(orgAddressEntry.getKeyPair(), orgAddressEntry.getContext(), offerId, true);
|
||||
addressEntryList.addAddressEntry(newEntry);
|
||||
return newEntry;
|
||||
AddressEntry cloneWithNewOfferId = new AddressEntry(sourceAddressEntry.getKeyPair(),
|
||||
sourceAddressEntry.getContext(),
|
||||
offerId,
|
||||
sourceAddressEntry.isSegwit());
|
||||
addressEntryList.addAddressEntry(cloneWithNewOfferId);
|
||||
return cloneWithNewOfferId;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -187,6 +187,10 @@ public final class OpenOffer implements Tradable {
|
||||
return state == State.DEACTIVATED;
|
||||
}
|
||||
|
||||
public boolean isActivated() {
|
||||
return !isDeactivated();
|
||||
}
|
||||
|
||||
public boolean isCanceled() {
|
||||
return state == State.CANCELED;
|
||||
}
|
||||
|
@ -381,16 +381,16 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
|
||||
public void placeOffer(Offer offer,
|
||||
double buyerSecurityDeposit,
|
||||
boolean useSavingsWallet,
|
||||
boolean useBatchOfferOco,
|
||||
boolean isSharedMakerFee,
|
||||
long triggerPrice,
|
||||
TransactionResultHandler resultHandler,
|
||||
ErrorMessageHandler errorMessageHandler) {
|
||||
checkNotNull(offer.getMakerFee(), "makerFee must not be null");
|
||||
checkArgument(!offer.isBsqSwapOffer());
|
||||
|
||||
int numClones = getOpenOffersByMakerFeeTxId(offer.getOfferFeePaymentTxId()).size();
|
||||
if (numClones > 10) {
|
||||
errorMessageHandler.handleErrorMessage("PlaceOffer prevented because cloned OCO offers count is " + numClones);
|
||||
int numClones = getOpenOffersByMakerFee(offer.getOfferFeePaymentTxId()).size();
|
||||
if (numClones >= 10) {
|
||||
errorMessageHandler.handleErrorMessage("Cannot create offer because maximum number of 10 cloned offers with shared maker fee is reached.");
|
||||
return;
|
||||
}
|
||||
|
||||
@ -399,14 +399,10 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
|
||||
buyerSecurityDeposit,
|
||||
createOfferService.getSellerSecurityDepositAsDouble(buyerSecurityDeposit));
|
||||
|
||||
if (useBatchOfferOco) {
|
||||
offer.setPriceFeedService(priceFeedService);
|
||||
}
|
||||
|
||||
PlaceOfferModel model = new PlaceOfferModel(offer,
|
||||
reservedFundsForOffer,
|
||||
useSavingsWallet,
|
||||
useBatchOfferOco,
|
||||
isSharedMakerFee,
|
||||
btcWalletService,
|
||||
tradeWalletService,
|
||||
bsqWalletService,
|
||||
@ -421,7 +417,7 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
|
||||
model,
|
||||
transaction -> {
|
||||
OpenOffer openOffer = new OpenOffer(offer, triggerPrice);
|
||||
if (useBatchOfferOco) {
|
||||
if (isSharedMakerFee) {
|
||||
openOffer.setState(OpenOffer.State.DEACTIVATED);
|
||||
}
|
||||
addOpenOfferToList(openOffer);
|
||||
@ -471,6 +467,12 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
|
||||
return;
|
||||
}
|
||||
|
||||
if (cannotActivateOffer(openOffer.getOffer())) {
|
||||
errorMessageHandler.handleErrorMessage("This cloned offer with shared maker fee cannot be activated because it uses the same payment method " +
|
||||
"and currency as another active offer.");
|
||||
return;
|
||||
}
|
||||
|
||||
// If there is not enough funds for a BsqSwapOffer we do not publish the offer, but still apply the state change.
|
||||
// Once the wallet gets funded the offer gets published automatically.
|
||||
if (isBsqSwapOfferLackingFunds(openOffer)) {
|
||||
@ -480,11 +482,6 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
|
||||
return;
|
||||
}
|
||||
|
||||
if (!canBeEnabled(openOffer.getOffer())) {
|
||||
log.info("{} cannot be enabled, as it has duplicate characteristics with another open offer", openOffer.getShortId());
|
||||
return;
|
||||
}
|
||||
|
||||
Offer offer = openOffer.getOffer();
|
||||
offerBookService.activateOffer(offer,
|
||||
() -> {
|
||||
@ -606,8 +603,14 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
|
||||
openOffer.setState(OpenOffer.State.CANCELED);
|
||||
removeOpenOfferFromList(openOffer);
|
||||
|
||||
if (!openOffer.getOffer().isBsqSwapOffer() && !safeRemovalOfOcoClone(openOffer)) {
|
||||
closedTradableManager.add(openOffer);
|
||||
if (!openOffer.getOffer().isBsqSwapOffer()) {
|
||||
// In case of an offer which has its maker fee shared with other offers, we do not add the openOffer
|
||||
// to history. Only when the last offer with that maker fee txId got removed we add it.
|
||||
// Only canceled offers which have lost maker fees are shown in history.
|
||||
// For that reason we also do not add BSQ offers.
|
||||
if (getOpenOffersByMakerFee(offer.getOfferFeePaymentTxId()).isEmpty()) {
|
||||
closedTradableManager.add(openOffer);
|
||||
}
|
||||
btcWalletService.resetAddressEntriesForOpenOffer(offer.getId());
|
||||
}
|
||||
log.info("onRemoved offerId={}", offer.getId());
|
||||
@ -625,12 +628,15 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
|
||||
log::error);
|
||||
});
|
||||
} else {
|
||||
// offer taken may have been OCO, in which case all its clones need to be removed
|
||||
getOpenOffersByMakerFeeTxId(offer.getOfferFeePaymentTxId()).forEach(openOffer -> {
|
||||
getOpenOffersByMakerFee(offer.getOfferFeePaymentTxId()).forEach(openOffer -> {
|
||||
removeOpenOfferFromList(openOffer);
|
||||
openOffer.setState(OpenOffer.State.CLOSED);
|
||||
if (!offer.getId().equals(openOffer.getId())) {
|
||||
btcWalletService.resetAddressEntriesForOpenOffer(openOffer.getId()); // cleanup OCO clone
|
||||
|
||||
if (offer.getId().equals(openOffer.getId())) {
|
||||
openOffer.setState(OpenOffer.State.CLOSED);
|
||||
} else {
|
||||
// We use CANCELED for the offers which have shared maker fee but have not been taken for the trade.
|
||||
openOffer.setState(OpenOffer.State.CANCELED);
|
||||
btcWalletService.resetAddressEntriesForOpenOffer(openOffer.getId());
|
||||
}
|
||||
offerBookService.removeOffer(openOffer.getOffer().getOfferPayloadBase(),
|
||||
() -> log.trace("Successfully removed offer"),
|
||||
@ -644,6 +650,24 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
|
||||
requestPersistence();
|
||||
}
|
||||
|
||||
public boolean cannotActivateOffer(Offer offer) {
|
||||
return openOffers.stream()
|
||||
.filter(openOffer -> !openOffer.getOffer().isBsqSwapOffer()) // We only handle non-BSQ offers
|
||||
.filter(openOffer -> !openOffer.getId().equals(offer.getId())) // our own offer gets skipped
|
||||
.filter(OpenOffer::isActivated) // we only check with activated offers
|
||||
.anyMatch(openOffer ->
|
||||
// Offers which share our maker fee will get checked if they have the same payment method
|
||||
// and currency.
|
||||
openOffer.getOffer().getOfferFeePaymentTxId().equals(offer.getOfferFeePaymentTxId()) &&
|
||||
openOffer.getOffer().getPaymentMethodId().equalsIgnoreCase(offer.getPaymentMethodId()) &&
|
||||
openOffer.getOffer().getCounterCurrencyCode().equalsIgnoreCase(offer.getCounterCurrencyCode()) &&
|
||||
openOffer.getOffer().getBaseCurrencyCode().equalsIgnoreCase(offer.getBaseCurrencyCode()));
|
||||
}
|
||||
|
||||
public boolean isOfferWithSharedMakerFee(OpenOffer openOffer) {
|
||||
return getOpenOffersByMakerFee(openOffer.getOffer().getOfferFeePaymentTxId()).size() > 1;
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Getters
|
||||
@ -661,13 +685,6 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
|
||||
return openOffers.stream().filter(e -> e.getId().equals(offerId)).findFirst();
|
||||
}
|
||||
|
||||
public List<OpenOffer> getOpenOffersByMakerFeeTxId(String makerFeeTxId) {
|
||||
return openOffers.stream()
|
||||
.filter(e -> !e.getOffer().isBsqSwapOffer() && e.getOffer().getOfferFeePaymentTxId()
|
||||
.equals(makerFeeTxId == null ? "" : makerFeeTxId))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// OfferPayload Availability
|
||||
@ -1185,23 +1202,16 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
|
||||
openOffer.isBsqSwapOfferHasMissingFunds();
|
||||
}
|
||||
|
||||
public boolean canBeEnabled(Offer newOffer) {
|
||||
// does the user have another open offer on the same currency, payment method, and reserved UTXO?
|
||||
return openOffers.stream().noneMatch(e ->
|
||||
!e.getOffer().isBsqSwapOffer() &&
|
||||
!e.isDeactivated() &&
|
||||
!e.getId().equals(newOffer.getId()) &&
|
||||
e.getOffer().getOfferFeePaymentTxId().equals(newOffer.getOfferFeePaymentTxId()) &&
|
||||
e.getOffer().getPaymentMethodId().equalsIgnoreCase(newOffer.getPaymentMethodId()) &&
|
||||
e.getOffer().getCounterCurrencyCode().equalsIgnoreCase(newOffer.getCounterCurrencyCode()) &&
|
||||
e.getOffer().getBaseCurrencyCode().equalsIgnoreCase(newOffer.getBaseCurrencyCode()));
|
||||
}
|
||||
|
||||
public boolean safeRemovalOfOcoClone(OpenOffer openOffer) {
|
||||
return getOpenOffersByMakerFeeTxId(openOffer.getOffer().getOfferFeePaymentTxId()).size() > 1;
|
||||
}
|
||||
|
||||
private boolean preventedFromPublishing(OpenOffer openOffer) {
|
||||
return openOffer.isDeactivated() || openOffer.isBsqSwapOfferHasMissingFunds() || !canBeEnabled(openOffer.getOffer());
|
||||
return openOffer.isDeactivated() ||
|
||||
openOffer.isBsqSwapOfferHasMissingFunds() ||
|
||||
cannotActivateOffer(openOffer.getOffer());
|
||||
}
|
||||
|
||||
private Set<OpenOffer> getOpenOffersByMakerFee(String makerFeeTxId) {
|
||||
return openOffers.stream()
|
||||
.filter(openOffer -> !openOffer.getOffer().isBsqSwapOffer() &&
|
||||
openOffer.getOffer().getOfferFeePaymentTxId().equals(makerFeeTxId))
|
||||
.collect(Collectors.toSet());
|
||||
}
|
||||
}
|
||||
|
@ -45,7 +45,7 @@ public class PlaceOfferModel implements Model {
|
||||
private final Offer offer;
|
||||
private final Coin reservedFundsForOffer;
|
||||
private final boolean useSavingsWallet;
|
||||
private final boolean useBatchOfferOco;
|
||||
private final boolean isSharedMakerFee;
|
||||
private final BtcWalletService walletService;
|
||||
private final TradeWalletService tradeWalletService;
|
||||
private final BsqWalletService bsqWalletService;
|
||||
@ -67,7 +67,7 @@ public class PlaceOfferModel implements Model {
|
||||
public PlaceOfferModel(Offer offer,
|
||||
Coin reservedFundsForOffer,
|
||||
boolean useSavingsWallet,
|
||||
boolean useBatchOfferOco,
|
||||
boolean isSharedMakerFee,
|
||||
BtcWalletService walletService,
|
||||
TradeWalletService tradeWalletService,
|
||||
BsqWalletService bsqWalletService,
|
||||
@ -81,7 +81,7 @@ public class PlaceOfferModel implements Model {
|
||||
this.offer = offer;
|
||||
this.reservedFundsForOffer = reservedFundsForOffer;
|
||||
this.useSavingsWallet = useSavingsWallet;
|
||||
this.useBatchOfferOco = useBatchOfferOco;
|
||||
this.isSharedMakerFee = isSharedMakerFee;
|
||||
this.walletService = walletService;
|
||||
this.tradeWalletService = tradeWalletService;
|
||||
this.bsqWalletService = bsqWalletService;
|
||||
|
@ -19,7 +19,7 @@ package bisq.core.offer.placeoffer.bisq_v1;
|
||||
|
||||
import bisq.core.offer.placeoffer.bisq_v1.tasks.AddToOfferBook;
|
||||
import bisq.core.offer.placeoffer.bisq_v1.tasks.CheckNumberOfUnconfirmedTransactions;
|
||||
import bisq.core.offer.placeoffer.bisq_v1.tasks.CloneMakerFeeOco;
|
||||
import bisq.core.offer.placeoffer.bisq_v1.tasks.CloneAddressEntryForSharedMakerFee;
|
||||
import bisq.core.offer.placeoffer.bisq_v1.tasks.CreateMakerFeeTx;
|
||||
import bisq.core.offer.placeoffer.bisq_v1.tasks.ValidateOffer;
|
||||
import bisq.core.trade.bisq_v1.TransactionResultHandler;
|
||||
@ -78,10 +78,10 @@ public class PlaceOfferProtocol {
|
||||
}
|
||||
);
|
||||
|
||||
if (model.isUseBatchOfferOco()) {
|
||||
if (model.isSharedMakerFee()) {
|
||||
taskRunner.addTasks(
|
||||
ValidateOffer.class,
|
||||
CloneMakerFeeOco.class
|
||||
CloneAddressEntryForSharedMakerFee.class
|
||||
);
|
||||
} else {
|
||||
taskRunner.addTasks(
|
||||
|
@ -33,45 +33,44 @@ import org.bitcoinj.core.TransactionOutput;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
public class CloneMakerFeeOco extends Task<PlaceOfferModel> {
|
||||
//
|
||||
public class CloneAddressEntryForSharedMakerFee extends Task<PlaceOfferModel> {
|
||||
@SuppressWarnings({"unused"})
|
||||
public CloneMakerFeeOco(TaskRunner<PlaceOfferModel> taskHandler, PlaceOfferModel model) {
|
||||
public CloneAddressEntryForSharedMakerFee(TaskRunner<PlaceOfferModel> taskHandler, PlaceOfferModel model) {
|
||||
super(taskHandler, model);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void run() {
|
||||
runInterceptHook();
|
||||
Offer newOcoOffer = model.getOffer();
|
||||
// newOcoOffer is cloned from an existing offer;
|
||||
// the clone needs a unique AddressEntry record associating the offerId with the reserved amount.
|
||||
|
||||
Offer offer = model.getOffer();
|
||||
String makerFeeTxId = offer.getOfferFeePaymentTxId();
|
||||
BtcWalletService walletService = model.getWalletService();
|
||||
for (AddressEntry potentialOcoSource : walletService.getAddressEntries(AddressEntry.Context.RESERVED_FOR_TRADE)) {
|
||||
getTxIdFromAddress(walletService, potentialOcoSource.getAddress()).ifPresent(txId -> {
|
||||
if (txId.equalsIgnoreCase(newOcoOffer.getOfferFeePaymentTxId())) {
|
||||
walletService.getOrCreateAddressEntry(potentialOcoSource, newOcoOffer.getId());
|
||||
newOcoOffer.setState(Offer.State.OFFER_FEE_PAID);
|
||||
complete();
|
||||
}
|
||||
});
|
||||
if (completed) {
|
||||
for (AddressEntry reservedForTradeEntry : walletService.getAddressEntries(AddressEntry.Context.RESERVED_FOR_TRADE)) {
|
||||
if (findTxId(reservedForTradeEntry.getAddress())
|
||||
.map(txId -> txId.equals(makerFeeTxId))
|
||||
.orElse(false)) {
|
||||
walletService.getOrCloneAddressEntryWithOfferId(reservedForTradeEntry, offer.getId());
|
||||
complete();
|
||||
return;
|
||||
}
|
||||
}
|
||||
failed();
|
||||
}
|
||||
|
||||
// AddressEntry and TxId are not linked, so do a reverse lookup
|
||||
private Optional<String> getTxIdFromAddress(BtcWalletService walletService, Address address) {
|
||||
List<Transaction> txns = walletService.getRecentTransactions(0, false);
|
||||
for (Transaction txn : txns) {
|
||||
for (TransactionOutput output : txn.getOutputs()) {
|
||||
// We look up the most recent transaction with unspent outputs associated with the given address and return
|
||||
// the txId if found.
|
||||
private Optional<String> findTxId(Address address) {
|
||||
BtcWalletService walletService = model.getWalletService();
|
||||
List<Transaction> transactions = walletService.getAllRecentTransactions(false);
|
||||
for (Transaction transaction : transactions) {
|
||||
for (TransactionOutput output : transaction.getOutputs()) {
|
||||
if (walletService.isTransactionOutputMine(output) && WalletService.isOutputScriptConvertibleToAddress(output)) {
|
||||
String addressString = WalletService.getAddressStringFromOutput(output);
|
||||
assert addressString != null;
|
||||
// make sure the output is still unspent
|
||||
if (addressString.equalsIgnoreCase(address.toString()) && output.getSpentBy() == null) {
|
||||
return Optional.of(txn.getTxId().toString());
|
||||
if (addressString != null && addressString.equals(address.toString()) && output.getSpentBy() == null) {
|
||||
return Optional.of(transaction.getTxId().toString());
|
||||
}
|
||||
}
|
||||
}
|
@ -101,7 +101,7 @@ shared.removeOffer=Remove offer
|
||||
shared.dontRemoveOffer=Don't remove offer
|
||||
shared.editOffer=Edit offer
|
||||
shared.duplicateOffer=Duplicate offer
|
||||
shared.cloneGroupedOfferOco=Clone as Grouped Offer
|
||||
shared.cloneOffer=Clone offer (share maker fee)
|
||||
shared.openLargeQRWindow=Open large QR code window
|
||||
shared.chooseTradingAccount=Choose trading account
|
||||
shared.faq=Visit FAQ page
|
||||
@ -366,7 +366,8 @@ offerbook.timeSinceSigning.tooltip.checkmark.buyBtc=buy BTC from a signed accoun
|
||||
offerbook.timeSinceSigning.tooltip.checkmark.wait=wait a minimum of {0} days
|
||||
offerbook.timeSinceSigning.tooltip.learnMore=Learn more
|
||||
offerbook.xmrAutoConf=Is auto-confirm enabled
|
||||
offerbook.toEnableOffer=Change ccy or payment method to enable offer.
|
||||
|
||||
offerbook.toEnableOffer=Change payment method or currency to enable cloned offer.
|
||||
|
||||
offerbook.timeSinceSigning.help=When you successfully complete a trade with a peer who has a signed payment account, your payment account is signed.\n\
|
||||
{0} days later, the initial limit of {1} is lifted and your account can sign other peers'' payment accounts.
|
||||
@ -657,7 +658,6 @@ portfolio.tab.bsqSwap=Unconfirmed BSQ swaps
|
||||
portfolio.tab.failed=Failed
|
||||
portfolio.tab.editOpenOffer=Edit offer
|
||||
portfolio.tab.duplicateOffer=Duplicate offer
|
||||
portfolio.context.offerLikeThis=Create new offer like this...
|
||||
portfolio.context.notYourOffer=You can only duplicate offers where you were the maker.
|
||||
|
||||
portfolio.closedTrades.deviation.help=Percentage price deviation from market
|
||||
|
@ -604,7 +604,7 @@ portfolio.tab.bsqSwap=Unconfirmed BSQ swaps
|
||||
portfolio.tab.failed=失敗
|
||||
portfolio.tab.editOpenOffer=編輯報價
|
||||
portfolio.tab.duplicateOffer=Duplicate offer
|
||||
portfolio.context.offerLikeThis=Create new offer like this...
|
||||
portfolio.context.offerLikeThis=Duplicate offer
|
||||
portfolio.context.notYourOffer=You can only duplicate offers where you were the maker.
|
||||
|
||||
portfolio.closedTrades.deviation.help=Percentage price deviation from market
|
||||
|
@ -264,7 +264,7 @@ public class ClosedTradesView extends ActivatableViewAndModel<VBox, ClosedTrades
|
||||
tableView -> {
|
||||
TableRow<ClosedTradesListItem> row = new TableRow<>();
|
||||
ContextMenu rowMenu = new ContextMenu();
|
||||
MenuItem duplicateItem = new MenuItem(Res.get("portfolio.context.offerLikeThis"));
|
||||
MenuItem duplicateItem = new MenuItem(Res.get("portfolio.tab.duplicateOffer"));
|
||||
duplicateItem.setOnAction((ActionEvent event) -> onDuplicateOffer(row.getItem().getTradable().getOffer()));
|
||||
rowMenu.getItems().add(duplicateItem);
|
||||
row.contextMenuProperty().bind(
|
||||
|
@ -134,6 +134,7 @@ class OpenOfferListItem implements FilterableListItem {
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
public String getOcoGroupForSorting() {
|
||||
Offer offer = getOffer();
|
||||
if (offer.isBsqSwapOffer()) {
|
||||
@ -142,6 +143,7 @@ class OpenOfferListItem implements FilterableListItem {
|
||||
return offer.getOfferFeePaymentTxId();
|
||||
}
|
||||
|
||||
//
|
||||
public String getOcoGroupForDisplay() {
|
||||
return getOcoGroupForSorting().substring(0, 4);
|
||||
}
|
||||
|
@ -41,6 +41,7 @@ import bisq.core.offer.Offer;
|
||||
import bisq.core.offer.OpenOffer;
|
||||
import bisq.core.offer.OpenOfferManager;
|
||||
import bisq.core.offer.bisq_v1.OfferPayload;
|
||||
import bisq.core.provider.price.PriceFeedService;
|
||||
import bisq.core.user.DontShowAgainLookup;
|
||||
|
||||
import com.googlecode.jcsv.writer.CSVEntryConverter;
|
||||
@ -139,6 +140,7 @@ public class OpenOffersView extends ActivatableViewAndModel<VBox, OpenOffersView
|
||||
@FXML
|
||||
AutoTooltipSlideToggleButton selectToggleButton;
|
||||
|
||||
private final PriceFeedService priceFeedService;
|
||||
private final Navigation navigation;
|
||||
private final OfferDetailsWindow offerDetailsWindow;
|
||||
private final BsqSwapOfferDetailsWindow bsqSwapOfferDetailsWindow;
|
||||
@ -151,10 +153,12 @@ public class OpenOffersView extends ActivatableViewAndModel<VBox, OpenOffersView
|
||||
@Inject
|
||||
public OpenOffersView(OpenOffersViewModel model,
|
||||
OpenOfferManager openOfferManager,
|
||||
PriceFeedService priceFeedService,
|
||||
Navigation navigation,
|
||||
OfferDetailsWindow offerDetailsWindow,
|
||||
BsqSwapOfferDetailsWindow bsqSwapOfferDetailsWindow) {
|
||||
super(model);
|
||||
this.priceFeedService = priceFeedService;
|
||||
this.navigation = navigation;
|
||||
this.offerDetailsWindow = offerDetailsWindow;
|
||||
this.bsqSwapOfferDetailsWindow = bsqSwapOfferDetailsWindow;
|
||||
@ -221,15 +225,13 @@ public class OpenOffersView extends ActivatableViewAndModel<VBox, OpenOffersView
|
||||
tableView -> {
|
||||
final TableRow<OpenOfferListItem> row = new TableRow<>();
|
||||
final ContextMenu rowMenu = new ContextMenu();
|
||||
MenuItem duplicateItem = new MenuItem(Res.get("portfolio.context.offerLikeThis"));
|
||||
duplicateItem.setOnAction((event) -> onDuplicateOffer(row.getItem()));
|
||||
MenuItem cloneGroupedOfferOco1 = new MenuItem(Res.get("shared.cloneGroupedOfferOco"));
|
||||
cloneGroupedOfferOco1.setOnAction((event) -> onDuplicateOfferOco(row.getItem(), 1));
|
||||
MenuItem cloneGroupedOfferOco5 = new MenuItem(Res.get("shared.cloneGroupedOfferOco") + " x5");
|
||||
cloneGroupedOfferOco5.setOnAction((event) -> onDuplicateOfferOco(row.getItem(), 5));
|
||||
rowMenu.getItems().add(duplicateItem);
|
||||
rowMenu.getItems().add(cloneGroupedOfferOco1);
|
||||
rowMenu.getItems().add(cloneGroupedOfferOco5);
|
||||
MenuItem duplicateOfferMenuItem = new MenuItem(Res.get("portfolio.tab.duplicateOffer"));
|
||||
duplicateOfferMenuItem.setOnAction((event) -> onDuplicateOffer(row.getItem()));
|
||||
//
|
||||
MenuItem cloneOfferMenuItem = new MenuItem(Res.get("shared.cloneOffer"));
|
||||
cloneOfferMenuItem.setOnAction((event) -> onCloneOffer(row.getItem().getOpenOffer()));
|
||||
rowMenu.getItems().add(duplicateOfferMenuItem);
|
||||
rowMenu.getItems().add(cloneOfferMenuItem);
|
||||
row.contextMenuProperty().bind(
|
||||
Bindings.when(Bindings.isNotNull(row.itemProperty()))
|
||||
.then(rowMenu)
|
||||
@ -386,7 +388,7 @@ public class OpenOffersView extends ActivatableViewAndModel<VBox, OpenOffersView
|
||||
private void onRemoveOpenOffer(OpenOfferListItem item) {
|
||||
OpenOffer openOffer = item.getOpenOffer();
|
||||
if (model.isBootstrappedOrShowPopup()) {
|
||||
if (openOfferManager.safeRemovalOfOcoClone(openOffer)) {
|
||||
if (openOfferManager.isOfferWithSharedMakerFee(openOffer)) {
|
||||
doRemoveOpenOffer(openOffer);
|
||||
} else {
|
||||
String key = (openOffer.getOffer().isBsqSwapOffer() ? "RemoveBsqSwapWarning" : "RemoveOfferWarning");
|
||||
@ -409,16 +411,17 @@ public class OpenOffersView extends ActivatableViewAndModel<VBox, OpenOffersView
|
||||
}
|
||||
|
||||
private void doRemoveOpenOffer(OpenOffer openOffer) {
|
||||
boolean isSafeRemovalOfOcoClone = openOfferManager.safeRemovalOfOcoClone(openOffer);
|
||||
model.onRemoveOpenOffer(openOffer,
|
||||
() -> {
|
||||
log.debug("Remove offer was successful");
|
||||
|
||||
tableView.refresh();
|
||||
|
||||
if (openOffer.getOffer().isBsqSwapOffer() || isSafeRemovalOfOcoClone) {
|
||||
return; // nothing to withdraw when Bsq swap is canceled (issue #5956)
|
||||
// We do not show the popup if it's a BSQ offer or a cloned offer with shared maker fee
|
||||
if (openOffer.getOffer().isBsqSwapOffer() || openOfferManager.isOfferWithSharedMakerFee(openOffer)) {
|
||||
return;
|
||||
}
|
||||
|
||||
String key = "WithdrawFundsAfterRemoveOfferInfo";
|
||||
if (DontShowAgainLookup.showAgain(key)) {
|
||||
new Popup().instruction(Res.get("offerbook.withdrawFundsHint", Res.get("navigation.funds.availableForWithdrawal")))
|
||||
@ -448,60 +451,64 @@ public class OpenOffersView extends ActivatableViewAndModel<VBox, OpenOffersView
|
||||
}
|
||||
}
|
||||
|
||||
private void onDuplicateOfferOco(OpenOfferListItem item, int numDuplicates) {
|
||||
for (int i=0; i< numDuplicates; i++) {
|
||||
item.getOffer().getOfferPayload().ifPresent(original -> {
|
||||
log.info("Duplicating offer as OCO: {}", original.getId());
|
||||
String newOfferId = getRandomOfferId();
|
||||
OfferPayload offerPayload = new OfferPayload(newOfferId,
|
||||
new Date().getTime(),
|
||||
original.getOwnerNodeAddress(),
|
||||
original.getPubKeyRing(),
|
||||
original.getDirection(),
|
||||
original.getPrice(),
|
||||
original.getMarketPriceMargin(),
|
||||
original.isUseMarketBasedPrice(),
|
||||
original.getAmount(),
|
||||
original.getMinAmount(),
|
||||
original.getBaseCurrencyCode(),
|
||||
original.getCounterCurrencyCode(),
|
||||
original.getArbitratorNodeAddresses(),
|
||||
original.getMediatorNodeAddresses(),
|
||||
original.getPaymentMethodId(),
|
||||
original.getMakerPaymentAccountId(),
|
||||
original.getOfferFeePaymentTxId(),
|
||||
original.getCountryCode(),
|
||||
original.getAcceptedCountryCodes(),
|
||||
original.getBankId(),
|
||||
original.getAcceptedBankIds(),
|
||||
original.getVersionNr(),
|
||||
original.getBlockHeightAtOfferCreation(),
|
||||
original.getTxFee(),
|
||||
original.getMakerFee(),
|
||||
original.isCurrencyForMakerFeeBtc(),
|
||||
original.getBuyerSecurityDeposit(),
|
||||
original.getSellerSecurityDeposit(),
|
||||
original.getMaxTradeLimit(),
|
||||
original.getMaxTradePeriod(),
|
||||
original.isUseAutoClose(),
|
||||
original.isUseReOpenAfterAutoClose(),
|
||||
original.getLowerClosePrice(),
|
||||
original.getUpperClosePrice(),
|
||||
original.isPrivateOffer(),
|
||||
original.getHashOfChallenge(),
|
||||
original.getExtraDataMap(),
|
||||
original.getProtocolVersion());
|
||||
Offer expandedOffer = new Offer(offerPayload);
|
||||
openOfferManager.placeOffer(expandedOffer,
|
||||
0,
|
||||
false,
|
||||
true,
|
||||
0,
|
||||
transaction -> {
|
||||
},
|
||||
log::error);
|
||||
});
|
||||
private void onCloneOffer(OpenOffer openOffer) {
|
||||
if (openOffer == null || openOffer.getOffer() == null || openOffer.getOffer().getOfferPayload().isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
Offer offer = openOffer.getOffer();
|
||||
OfferPayload sourceOfferPayload = offer.getOfferPayload().get();
|
||||
log.info("Clone offerPayload with shared maker fee: {}", sourceOfferPayload.getId());
|
||||
String newOfferId = getRandomOfferId();
|
||||
OfferPayload duplicatedOfferPayload = new OfferPayload(newOfferId,
|
||||
new Date().getTime(),
|
||||
sourceOfferPayload.getOwnerNodeAddress(),
|
||||
sourceOfferPayload.getPubKeyRing(),
|
||||
sourceOfferPayload.getDirection(),
|
||||
sourceOfferPayload.getPrice(),
|
||||
sourceOfferPayload.getMarketPriceMargin(),
|
||||
sourceOfferPayload.isUseMarketBasedPrice(),
|
||||
sourceOfferPayload.getAmount(),
|
||||
sourceOfferPayload.getMinAmount(),
|
||||
sourceOfferPayload.getBaseCurrencyCode(),
|
||||
sourceOfferPayload.getCounterCurrencyCode(),
|
||||
sourceOfferPayload.getArbitratorNodeAddresses(),
|
||||
sourceOfferPayload.getMediatorNodeAddresses(),
|
||||
sourceOfferPayload.getPaymentMethodId(),
|
||||
sourceOfferPayload.getMakerPaymentAccountId(),
|
||||
sourceOfferPayload.getOfferFeePaymentTxId(),
|
||||
sourceOfferPayload.getCountryCode(),
|
||||
sourceOfferPayload.getAcceptedCountryCodes(),
|
||||
sourceOfferPayload.getBankId(),
|
||||
sourceOfferPayload.getAcceptedBankIds(),
|
||||
sourceOfferPayload.getVersionNr(),
|
||||
sourceOfferPayload.getBlockHeightAtOfferCreation(),
|
||||
sourceOfferPayload.getTxFee(),
|
||||
sourceOfferPayload.getMakerFee(),
|
||||
sourceOfferPayload.isCurrencyForMakerFeeBtc(),
|
||||
sourceOfferPayload.getBuyerSecurityDeposit(),
|
||||
sourceOfferPayload.getSellerSecurityDeposit(),
|
||||
sourceOfferPayload.getMaxTradeLimit(),
|
||||
sourceOfferPayload.getMaxTradePeriod(),
|
||||
sourceOfferPayload.isUseAutoClose(),
|
||||
sourceOfferPayload.isUseReOpenAfterAutoClose(),
|
||||
sourceOfferPayload.getLowerClosePrice(),
|
||||
sourceOfferPayload.getUpperClosePrice(),
|
||||
sourceOfferPayload.isPrivateOffer(),
|
||||
sourceOfferPayload.getHashOfChallenge(),
|
||||
sourceOfferPayload.getExtraDataMap(),
|
||||
sourceOfferPayload.getProtocolVersion());
|
||||
Offer clonedOffer = new Offer(duplicatedOfferPayload);
|
||||
clonedOffer.setPriceFeedService(priceFeedService);
|
||||
offer.setState(Offer.State.OFFER_FEE_PAID);
|
||||
openOfferManager.placeOffer(clonedOffer,
|
||||
clonedOffer.getBuyerSecurityDeposit().getValue(),
|
||||
false,
|
||||
true,
|
||||
openOffer.getTriggerPrice(),
|
||||
transaction -> {
|
||||
},
|
||||
log::error);
|
||||
}
|
||||
|
||||
private void setOfferIdColumnCellFactory() {
|
||||
@ -684,7 +691,7 @@ public class OpenOffersView extends ActivatableViewAndModel<VBox, OpenOffersView
|
||||
if (item != null) {
|
||||
if (item.isNotPublished()) getStyleClass().add("offer-disabled");
|
||||
Label label = new AutoTooltipLabel(item.getOcoGroupForDisplay());
|
||||
if (!openOfferManager.canBeEnabled(item.getOpenOffer().getOffer())) {
|
||||
if (openOfferManager.cannotActivateOffer(item.getOpenOffer().getOffer())) {
|
||||
Text icon = getRegularIconForLabel(MaterialDesignIcon.EYE_OFF, label, "opaque-icon");
|
||||
label.setContentDisplay(ContentDisplay.RIGHT);
|
||||
Tooltip.install(icon, new Tooltip(Res.get("offerbook.toEnableOffer")));
|
||||
|
@ -234,7 +234,7 @@ public class PendingTradesView extends ActivatableViewAndModel<VBox, PendingTrad
|
||||
tableView -> {
|
||||
final TableRow<PendingTradesListItem> row = new TableRow<>();
|
||||
final ContextMenu rowMenu = new ContextMenu();
|
||||
MenuItem duplicateItem = new MenuItem(Res.get("portfolio.context.offerLikeThis"));
|
||||
MenuItem duplicateItem = new MenuItem(Res.get("portfolio.tab.duplicateOffer"));
|
||||
duplicateItem.setOnAction((event) -> {
|
||||
try {
|
||||
OfferPayload offerPayload = row.getItem().getTrade().getOffer().getOfferPayload().orElseThrow();
|
||||
|
Loading…
Reference in New Issue
Block a user