From 94ab3c1f9b68041e6390411a7975cd7631206178 Mon Sep 17 00:00:00 2001 From: woodser Date: Sun, 28 Apr 2024 19:53:10 -0400 Subject: [PATCH] show reserved amount in maker's offer details --- .../main/java/haveno/core/offer/Offer.java | 18 ++++++++++----- .../haveno/core/offer/OpenOfferManager.java | 22 +++++++++---------- .../java/haveno/core/trade/HavenoUtils.java | 4 +++- .../core/xmr/wallet/XmrWalletService.java | 11 ++++++++++ .../resources/i18n/displayStrings.properties | 1 + .../overlays/windows/OfferDetailsWindow.java | 17 ++++++++++---- 6 files changed, 51 insertions(+), 22 deletions(-) diff --git a/core/src/main/java/haveno/core/offer/Offer.java b/core/src/main/java/haveno/core/offer/Offer.java index 2c73259a7f..3b301869c1 100644 --- a/core/src/main/java/haveno/core/offer/Offer.java +++ b/core/src/main/java/haveno/core/offer/Offer.java @@ -282,12 +282,18 @@ public class Offer implements NetworkPayload, PersistablePayload { // Getter /////////////////////////////////////////////////////////////////////////////////////////// - // get the amount needed for the maker to reserve the offer - public BigInteger getReserveAmount() { - BigInteger reserveAmount = getDirection() == OfferDirection.BUY ? getMaxBuyerSecurityDeposit() : getMaxSellerSecurityDeposit(); - if (getDirection() == OfferDirection.SELL) reserveAmount = reserveAmount.add(getAmount()); - reserveAmount = reserveAmount.add(getMaxMakerFee()); - return reserveAmount; + // amount needed for the maker to reserve the offer + public BigInteger getAmountNeeded() { + BigInteger amountNeeded = getDirection() == OfferDirection.BUY ? getMaxBuyerSecurityDeposit() : getMaxSellerSecurityDeposit(); + if (getDirection() == OfferDirection.SELL) amountNeeded = amountNeeded.add(getAmount()); + amountNeeded = amountNeeded.add(getMaxMakerFee()); + return amountNeeded; + } + + // amount reserved for offer + public BigInteger getReservedAmount() { + if (offerPayload.getReserveTxKeyImages() == null) return null; + return HavenoUtils.xmrWalletService.getOutputsAmount(offerPayload.getReserveTxKeyImages()); } public BigInteger getMaxMakerFee() { diff --git a/core/src/main/java/haveno/core/offer/OpenOfferManager.java b/core/src/main/java/haveno/core/offer/OpenOfferManager.java index eefe8a7895..3750ffdfb5 100644 --- a/core/src/main/java/haveno/core/offer/OpenOfferManager.java +++ b/core/src/main/java/haveno/core/offer/OpenOfferManager.java @@ -879,8 +879,8 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe return; } - // get offer reserve amount - BigInteger offerReserveAmount = openOffer.getOffer().getReserveAmount(); + // get amount needed to reserve offer + BigInteger amountNeeded = openOffer.getOffer().getAmountNeeded(); // handle split output offer if (openOffer.isReserveExactAmount()) { @@ -889,13 +889,13 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe MoneroTxWallet splitOutputTx = findSplitOutputFundingTx(openOffers, openOffer); if (splitOutputTx != null && openOffer.getScheduledTxHashes() == null) { openOffer.setScheduledTxHashes(Arrays.asList(splitOutputTx.getHash())); - openOffer.setScheduledAmount(offerReserveAmount.toString()); + openOffer.setScheduledAmount(amountNeeded.toString()); openOffer.setState(OpenOffer.State.SCHEDULED); } // if not found, create tx to split exact output if (splitOutputTx == null) { - splitOrSchedule(openOffers, openOffer, offerReserveAmount); + splitOrSchedule(openOffers, openOffer, amountNeeded); } else if (!splitOutputTx.isLocked()) { // otherwise sign and post offer if split output available @@ -905,7 +905,7 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe if (openOffer.getSplitOutputTxHash() == null) { int offerSubaddress = xmrWalletService.getOrCreateAddressEntry(openOffer.getId(), XmrAddressEntry.Context.OFFER_FUNDING).getSubaddressIndex(); log.warn("Splitting new output because spending scheduled output(s) failed for offer {}. Offer funding subadress={}", openOffer.getId(), offerSubaddress); - splitOrSchedule(openOffers, openOffer, offerReserveAmount); + splitOrSchedule(openOffers, openOffer, amountNeeded); resultHandler.handleResult(null); } else { errorMessageHandler.handleErrorMessage(errMsg); @@ -916,7 +916,7 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe } else { // handle sufficient balance - boolean hasSufficientBalance = xmrWalletService.getWallet().getUnlockedBalance(0).compareTo(offerReserveAmount) >= 0; + boolean hasSufficientBalance = xmrWalletService.getWallet().getUnlockedBalance(0).compareTo(amountNeeded) >= 0; if (hasSufficientBalance) { signAndPostOffer(openOffer, true, resultHandler, errorMessageHandler); return; @@ -936,7 +936,7 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe private MoneroTxWallet findSplitOutputFundingTx(List openOffers, OpenOffer openOffer) { XmrAddressEntry addressEntry = xmrWalletService.getOrCreateAddressEntry(openOffer.getId(), XmrAddressEntry.Context.OFFER_FUNDING); - return findSplitOutputFundingTx(openOffers, openOffer, openOffer.getOffer().getReserveAmount(), addressEntry.getSubaddressIndex()); + return findSplitOutputFundingTx(openOffers, openOffer, openOffer.getOffer().getAmountNeeded(), addressEntry.getSubaddressIndex()); } private MoneroTxWallet findSplitOutputFundingTx(List openOffers, OpenOffer openOffer, BigInteger reserveAmount, Integer preferredSubaddressIndex) { @@ -1035,7 +1035,7 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe } private MoneroTxWallet splitAndSchedule(OpenOffer openOffer) { - BigInteger reserveAmount = openOffer.getOffer().getReserveAmount(); + BigInteger reserveAmount = openOffer.getOffer().getAmountNeeded(); xmrWalletService.swapAddressEntryToAvailable(openOffer.getId(), XmrAddressEntry.Context.OFFER_FUNDING); // change funding subaddress in case funded with unsuitable output(s) MoneroTxWallet splitOutputTx = null; synchronized (XmrWalletService.WALLET_LOCK) { @@ -1064,7 +1064,7 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe openOffer.setSplitOutputTxHash(splitOutputTx.getHash()); openOffer.setSplitOutputTxFee(splitOutputTx.getFee().longValueExact()); openOffer.setScheduledTxHashes(Arrays.asList(splitOutputTx.getHash())); - openOffer.setScheduledAmount(openOffer.getOffer().getReserveAmount().toString()); + openOffer.setScheduledAmount(openOffer.getOffer().getAmountNeeded().toString()); openOffer.setState(OpenOffer.State.SCHEDULED); return splitOutputTx; } @@ -1072,7 +1072,7 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe private void scheduleWithEarliestTxs(List openOffers, OpenOffer openOffer) { // check for sufficient balance - scheduled offers amount - BigInteger offerReserveAmount = openOffer.getOffer().getReserveAmount(); + BigInteger offerReserveAmount = openOffer.getOffer().getAmountNeeded(); if (xmrWalletService.getWallet().getBalance(0).subtract(getScheduledAmount(openOffers)).compareTo(offerReserveAmount) < 0) { throw new RuntimeException("Not enough money in Haveno wallet"); } @@ -1135,7 +1135,7 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe // create model PlaceOfferModel model = new PlaceOfferModel(openOffer, - openOffer.getOffer().getReserveAmount(), + openOffer.getOffer().getAmountNeeded(), useSavingsWallet, p2PService, btcWalletService, diff --git a/core/src/main/java/haveno/core/trade/HavenoUtils.java b/core/src/main/java/haveno/core/trade/HavenoUtils.java index 2a0c73f72a..bc0c2b85c2 100644 --- a/core/src/main/java/haveno/core/trade/HavenoUtils.java +++ b/core/src/main/java/haveno/core/trade/HavenoUtils.java @@ -36,6 +36,7 @@ import haveno.core.trade.messages.InitTradeRequest; import haveno.core.trade.messages.PaymentReceivedMessage; import haveno.core.trade.messages.PaymentSentMessage; import haveno.core.util.JsonUtil; +import haveno.core.xmr.wallet.XmrWalletService; import haveno.network.p2p.NodeAddress; import java.math.BigDecimal; import java.math.BigInteger; @@ -93,8 +94,9 @@ public class HavenoUtils { public static final DecimalFormat XMR_FORMATTER = new DecimalFormat("##############0.000000000000", DECIMAL_FORMAT_SYMBOLS); public static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("dd-MM-yyyy HH:mm:ss"); - public static ArbitrationManager arbitrationManager; // TODO: better way to share references? public static HavenoSetup havenoSetup; + public static ArbitrationManager arbitrationManager; // TODO: better way to share references? + public static XmrWalletService xmrWalletService; public static boolean isSeedNode() { return havenoSetup == null; diff --git a/core/src/main/java/haveno/core/xmr/wallet/XmrWalletService.java b/core/src/main/java/haveno/core/xmr/wallet/XmrWalletService.java index 9f154931c8..2a33a6b84b 100644 --- a/core/src/main/java/haveno/core/xmr/wallet/XmrWalletService.java +++ b/core/src/main/java/haveno/core/xmr/wallet/XmrWalletService.java @@ -82,6 +82,7 @@ import monero.common.TaskLooper; import monero.daemon.MoneroDaemonRpc; import monero.daemon.model.MoneroDaemonInfo; import monero.daemon.model.MoneroFeeEstimate; +import monero.daemon.model.MoneroKeyImage; import monero.daemon.model.MoneroNetworkType; import monero.daemon.model.MoneroOutput; import monero.daemon.model.MoneroSubmitTxResult; @@ -196,6 +197,7 @@ public class XmrWalletService { this.rpcBindPort = rpcBindPort; this.useNativeXmrWallet = useNativeXmrWallet; this.xmrWalletFile = new File(walletDir, MONERO_WALLET_NAME); + HavenoUtils.xmrWalletService = this; // set monero logging if (MONERO_LOG_LEVEL >= 0) MoneroUtils.setLogLevel(MONERO_LOG_LEVEL); @@ -535,6 +537,15 @@ public class XmrWalletService { } } + public BigInteger getOutputsAmount(Collection keyImages) { + BigInteger sum = BigInteger.ZERO; + for (String keyImage : keyImages) { + List outputs = getOutputs(new MoneroOutputQuery().setIsSpent(false).setKeyImage(new MoneroKeyImage(keyImage))); + if (!outputs.isEmpty()) sum = sum.add(outputs.get(0).getAmount()); + } + return sum; + } + private List getSubaddressesWithExactInput(BigInteger amount) { // fetch unspent, unfrozen, unlocked outputs diff --git a/core/src/main/resources/i18n/displayStrings.properties b/core/src/main/resources/i18n/displayStrings.properties index c0aa44c8a1..9b83175fd3 100644 --- a/core/src/main/resources/i18n/displayStrings.properties +++ b/core/src/main/resources/i18n/displayStrings.properties @@ -173,6 +173,7 @@ shared.acceptedTakerCountries=Accepted taker countries shared.tradePrice=Trade price shared.tradeAmount=Trade amount shared.tradeVolume=Trade volume +shared.reservedAmount=Reserved amount shared.invalidKey=The key you entered was not correct. shared.enterPrivKey=Enter private key to unlock shared.payoutTxId=Payout transaction ID diff --git a/desktop/src/main/java/haveno/desktop/main/overlays/windows/OfferDetailsWindow.java b/desktop/src/main/java/haveno/desktop/main/overlays/windows/OfferDetailsWindow.java index 8e0a0c099b..1814fc8f62 100644 --- a/desktop/src/main/java/haveno/desktop/main/overlays/windows/OfferDetailsWindow.java +++ b/desktop/src/main/java/haveno/desktop/main/overlays/windows/OfferDetailsWindow.java @@ -212,14 +212,14 @@ public class OfferDetailsWindow extends Overlay { addConfirmationLabelLabel(gridPane, rowIndex, offerTypeLabel, DisplayUtils.getDirectionBothSides(direction), firstRowDistance); } - String btcAmount = Res.get("shared.xmrAmount"); + String amount = Res.get("shared.xmrAmount"); if (takeOfferHandlerOptional.isPresent()) { - addConfirmationLabelLabel(gridPane, ++rowIndex, btcAmount + xmrDirectionInfo, + addConfirmationLabelLabel(gridPane, ++rowIndex, amount + xmrDirectionInfo, HavenoUtils.formatXmr(tradeAmount, true)); addConfirmationLabelLabel(gridPane, ++rowIndex, VolumeUtil.formatVolumeLabel(currencyCode) + counterCurrencyDirectionInfo, VolumeUtil.formatVolumeWithCode(offer.getVolumeByAmount(tradeAmount))); } else { - addConfirmationLabelLabel(gridPane, ++rowIndex, btcAmount + xmrDirectionInfo, + addConfirmationLabelLabel(gridPane, ++rowIndex, amount + xmrDirectionInfo, HavenoUtils.formatXmr(offer.getAmount(), true)); addConfirmationLabelLabel(gridPane, ++rowIndex, Res.get("offerDetailsWindow.minXmrAmount"), HavenoUtils.formatXmr(offer.getMinAmount(), true)); @@ -257,7 +257,8 @@ public class OfferDetailsWindow extends Overlay { final String makerPaymentAccountId = offer.getMakerPaymentAccountId(); final PaymentAccount myPaymentAccount = user.getPaymentAccount(makerPaymentAccountId); String countryCode = offer.getCountryCode(); - if (offer.isMyOffer(keyRing) && makerPaymentAccountId != null && myPaymentAccount != null) { + boolean isMyOffer = offer.isMyOffer(keyRing); + if (isMyOffer && makerPaymentAccountId != null && myPaymentAccount != null) { addConfirmationLabelLabel(gridPane, ++rowIndex, Res.get("offerDetailsWindow.myTradingAccount"), myPaymentAccount.getAccountName()); } else { final String method = Res.get(paymentMethod.getId()); @@ -316,11 +317,16 @@ public class OfferDetailsWindow extends Overlay { textArea.setEditable(false); } + // get amount reserved for the offer + BigInteger reservedAmount = isMyOffer ? offer.getReservedAmount() : null; + rows = 3; if (countryCode != null) rows++; if (!isF2F) rows++; + if (reservedAmount != null) + rows++; addTitledGroupBg(gridPane, ++rowIndex, rows, Res.get("shared.details"), Layout.GROUP_DISTANCE); addConfirmationLabelTextFieldWithCopyIcon(gridPane, rowIndex, Res.get("shared.offerId"), offer.getId(), @@ -337,6 +343,9 @@ public class OfferDetailsWindow extends Overlay { " " + HavenoUtils.formatXmr(offer.getOfferPayload().getMaxSellerSecurityDeposit(), true); addConfirmationLabelLabel(gridPane, ++rowIndex, Res.get("shared.securityDeposit"), value); + if (reservedAmount != null) { + addConfirmationLabelLabel(gridPane, ++rowIndex, Res.get("shared.reservedAmount"), HavenoUtils.formatXmr(reservedAmount, true)); + } if (countryCode != null && !isF2F) addConfirmationLabelLabel(gridPane, ++rowIndex, Res.get("offerDetailsWindow.countryBank"),