Various refactorings and changes

Signed-off-by: HenrikJannsen <boilingfrog@gmx.com>
This commit is contained in:
HenrikJannsen 2023-05-04 18:30:09 +07:00
parent cadf2073cf
commit 992854c9b9
No known key found for this signature in database
GPG Key ID: 02AA2BAE387C8307
13 changed files with 182 additions and 155 deletions

View File

@ -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)

View File

@ -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;
}
}

View File

@ -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;
}

View File

@ -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());
}
}

View File

@ -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;

View File

@ -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(

View File

@ -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());
}
}
}

View File

@ -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

View File

@ -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

View File

@ -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(

View File

@ -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);
}

View File

@ -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")));

View File

@ -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();