show reserved amount in maker's offer details

This commit is contained in:
woodser 2024-04-28 19:53:10 -04:00
parent e63141279c
commit 94ab3c1f9b
6 changed files with 51 additions and 22 deletions

View File

@ -282,12 +282,18 @@ public class Offer implements NetworkPayload, PersistablePayload {
// Getter // Getter
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
// get the amount needed for the maker to reserve the offer // amount needed for the maker to reserve the offer
public BigInteger getReserveAmount() { public BigInteger getAmountNeeded() {
BigInteger reserveAmount = getDirection() == OfferDirection.BUY ? getMaxBuyerSecurityDeposit() : getMaxSellerSecurityDeposit(); BigInteger amountNeeded = getDirection() == OfferDirection.BUY ? getMaxBuyerSecurityDeposit() : getMaxSellerSecurityDeposit();
if (getDirection() == OfferDirection.SELL) reserveAmount = reserveAmount.add(getAmount()); if (getDirection() == OfferDirection.SELL) amountNeeded = amountNeeded.add(getAmount());
reserveAmount = reserveAmount.add(getMaxMakerFee()); amountNeeded = amountNeeded.add(getMaxMakerFee());
return reserveAmount; return amountNeeded;
}
// amount reserved for offer
public BigInteger getReservedAmount() {
if (offerPayload.getReserveTxKeyImages() == null) return null;
return HavenoUtils.xmrWalletService.getOutputsAmount(offerPayload.getReserveTxKeyImages());
} }
public BigInteger getMaxMakerFee() { public BigInteger getMaxMakerFee() {

View File

@ -879,8 +879,8 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
return; return;
} }
// get offer reserve amount // get amount needed to reserve offer
BigInteger offerReserveAmount = openOffer.getOffer().getReserveAmount(); BigInteger amountNeeded = openOffer.getOffer().getAmountNeeded();
// handle split output offer // handle split output offer
if (openOffer.isReserveExactAmount()) { if (openOffer.isReserveExactAmount()) {
@ -889,13 +889,13 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
MoneroTxWallet splitOutputTx = findSplitOutputFundingTx(openOffers, openOffer); MoneroTxWallet splitOutputTx = findSplitOutputFundingTx(openOffers, openOffer);
if (splitOutputTx != null && openOffer.getScheduledTxHashes() == null) { if (splitOutputTx != null && openOffer.getScheduledTxHashes() == null) {
openOffer.setScheduledTxHashes(Arrays.asList(splitOutputTx.getHash())); openOffer.setScheduledTxHashes(Arrays.asList(splitOutputTx.getHash()));
openOffer.setScheduledAmount(offerReserveAmount.toString()); openOffer.setScheduledAmount(amountNeeded.toString());
openOffer.setState(OpenOffer.State.SCHEDULED); openOffer.setState(OpenOffer.State.SCHEDULED);
} }
// if not found, create tx to split exact output // if not found, create tx to split exact output
if (splitOutputTx == null) { if (splitOutputTx == null) {
splitOrSchedule(openOffers, openOffer, offerReserveAmount); splitOrSchedule(openOffers, openOffer, amountNeeded);
} else if (!splitOutputTx.isLocked()) { } else if (!splitOutputTx.isLocked()) {
// otherwise sign and post offer if split output available // otherwise sign and post offer if split output available
@ -905,7 +905,7 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
if (openOffer.getSplitOutputTxHash() == null) { if (openOffer.getSplitOutputTxHash() == null) {
int offerSubaddress = xmrWalletService.getOrCreateAddressEntry(openOffer.getId(), XmrAddressEntry.Context.OFFER_FUNDING).getSubaddressIndex(); 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); 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); resultHandler.handleResult(null);
} else { } else {
errorMessageHandler.handleErrorMessage(errMsg); errorMessageHandler.handleErrorMessage(errMsg);
@ -916,7 +916,7 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
} else { } else {
// handle sufficient balance // handle sufficient balance
boolean hasSufficientBalance = xmrWalletService.getWallet().getUnlockedBalance(0).compareTo(offerReserveAmount) >= 0; boolean hasSufficientBalance = xmrWalletService.getWallet().getUnlockedBalance(0).compareTo(amountNeeded) >= 0;
if (hasSufficientBalance) { if (hasSufficientBalance) {
signAndPostOffer(openOffer, true, resultHandler, errorMessageHandler); signAndPostOffer(openOffer, true, resultHandler, errorMessageHandler);
return; return;
@ -936,7 +936,7 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
private MoneroTxWallet findSplitOutputFundingTx(List<OpenOffer> openOffers, OpenOffer openOffer) { private MoneroTxWallet findSplitOutputFundingTx(List<OpenOffer> openOffers, OpenOffer openOffer) {
XmrAddressEntry addressEntry = xmrWalletService.getOrCreateAddressEntry(openOffer.getId(), XmrAddressEntry.Context.OFFER_FUNDING); 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<OpenOffer> openOffers, OpenOffer openOffer, BigInteger reserveAmount, Integer preferredSubaddressIndex) { private MoneroTxWallet findSplitOutputFundingTx(List<OpenOffer> openOffers, OpenOffer openOffer, BigInteger reserveAmount, Integer preferredSubaddressIndex) {
@ -1035,7 +1035,7 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
} }
private MoneroTxWallet splitAndSchedule(OpenOffer openOffer) { 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) xmrWalletService.swapAddressEntryToAvailable(openOffer.getId(), XmrAddressEntry.Context.OFFER_FUNDING); // change funding subaddress in case funded with unsuitable output(s)
MoneroTxWallet splitOutputTx = null; MoneroTxWallet splitOutputTx = null;
synchronized (XmrWalletService.WALLET_LOCK) { synchronized (XmrWalletService.WALLET_LOCK) {
@ -1064,7 +1064,7 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
openOffer.setSplitOutputTxHash(splitOutputTx.getHash()); openOffer.setSplitOutputTxHash(splitOutputTx.getHash());
openOffer.setSplitOutputTxFee(splitOutputTx.getFee().longValueExact()); openOffer.setSplitOutputTxFee(splitOutputTx.getFee().longValueExact());
openOffer.setScheduledTxHashes(Arrays.asList(splitOutputTx.getHash())); openOffer.setScheduledTxHashes(Arrays.asList(splitOutputTx.getHash()));
openOffer.setScheduledAmount(openOffer.getOffer().getReserveAmount().toString()); openOffer.setScheduledAmount(openOffer.getOffer().getAmountNeeded().toString());
openOffer.setState(OpenOffer.State.SCHEDULED); openOffer.setState(OpenOffer.State.SCHEDULED);
return splitOutputTx; return splitOutputTx;
} }
@ -1072,7 +1072,7 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
private void scheduleWithEarliestTxs(List<OpenOffer> openOffers, OpenOffer openOffer) { private void scheduleWithEarliestTxs(List<OpenOffer> openOffers, OpenOffer openOffer) {
// check for sufficient balance - scheduled offers amount // 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) { if (xmrWalletService.getWallet().getBalance(0).subtract(getScheduledAmount(openOffers)).compareTo(offerReserveAmount) < 0) {
throw new RuntimeException("Not enough money in Haveno wallet"); throw new RuntimeException("Not enough money in Haveno wallet");
} }
@ -1135,7 +1135,7 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
// create model // create model
PlaceOfferModel model = new PlaceOfferModel(openOffer, PlaceOfferModel model = new PlaceOfferModel(openOffer,
openOffer.getOffer().getReserveAmount(), openOffer.getOffer().getAmountNeeded(),
useSavingsWallet, useSavingsWallet,
p2PService, p2PService,
btcWalletService, btcWalletService,

View File

@ -36,6 +36,7 @@ import haveno.core.trade.messages.InitTradeRequest;
import haveno.core.trade.messages.PaymentReceivedMessage; import haveno.core.trade.messages.PaymentReceivedMessage;
import haveno.core.trade.messages.PaymentSentMessage; import haveno.core.trade.messages.PaymentSentMessage;
import haveno.core.util.JsonUtil; import haveno.core.util.JsonUtil;
import haveno.core.xmr.wallet.XmrWalletService;
import haveno.network.p2p.NodeAddress; import haveno.network.p2p.NodeAddress;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.math.BigInteger; 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 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 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 HavenoSetup havenoSetup;
public static ArbitrationManager arbitrationManager; // TODO: better way to share references?
public static XmrWalletService xmrWalletService;
public static boolean isSeedNode() { public static boolean isSeedNode() {
return havenoSetup == null; return havenoSetup == null;

View File

@ -82,6 +82,7 @@ import monero.common.TaskLooper;
import monero.daemon.MoneroDaemonRpc; import monero.daemon.MoneroDaemonRpc;
import monero.daemon.model.MoneroDaemonInfo; import monero.daemon.model.MoneroDaemonInfo;
import monero.daemon.model.MoneroFeeEstimate; import monero.daemon.model.MoneroFeeEstimate;
import monero.daemon.model.MoneroKeyImage;
import monero.daemon.model.MoneroNetworkType; import monero.daemon.model.MoneroNetworkType;
import monero.daemon.model.MoneroOutput; import monero.daemon.model.MoneroOutput;
import monero.daemon.model.MoneroSubmitTxResult; import monero.daemon.model.MoneroSubmitTxResult;
@ -196,6 +197,7 @@ public class XmrWalletService {
this.rpcBindPort = rpcBindPort; this.rpcBindPort = rpcBindPort;
this.useNativeXmrWallet = useNativeXmrWallet; this.useNativeXmrWallet = useNativeXmrWallet;
this.xmrWalletFile = new File(walletDir, MONERO_WALLET_NAME); this.xmrWalletFile = new File(walletDir, MONERO_WALLET_NAME);
HavenoUtils.xmrWalletService = this;
// set monero logging // set monero logging
if (MONERO_LOG_LEVEL >= 0) MoneroUtils.setLogLevel(MONERO_LOG_LEVEL); if (MONERO_LOG_LEVEL >= 0) MoneroUtils.setLogLevel(MONERO_LOG_LEVEL);
@ -535,6 +537,15 @@ public class XmrWalletService {
} }
} }
public BigInteger getOutputsAmount(Collection<String> keyImages) {
BigInteger sum = BigInteger.ZERO;
for (String keyImage : keyImages) {
List<MoneroOutputWallet> outputs = getOutputs(new MoneroOutputQuery().setIsSpent(false).setKeyImage(new MoneroKeyImage(keyImage)));
if (!outputs.isEmpty()) sum = sum.add(outputs.get(0).getAmount());
}
return sum;
}
private List<Integer> getSubaddressesWithExactInput(BigInteger amount) { private List<Integer> getSubaddressesWithExactInput(BigInteger amount) {
// fetch unspent, unfrozen, unlocked outputs // fetch unspent, unfrozen, unlocked outputs

View File

@ -173,6 +173,7 @@ shared.acceptedTakerCountries=Accepted taker countries
shared.tradePrice=Trade price shared.tradePrice=Trade price
shared.tradeAmount=Trade amount shared.tradeAmount=Trade amount
shared.tradeVolume=Trade volume shared.tradeVolume=Trade volume
shared.reservedAmount=Reserved amount
shared.invalidKey=The key you entered was not correct. shared.invalidKey=The key you entered was not correct.
shared.enterPrivKey=Enter private key to unlock shared.enterPrivKey=Enter private key to unlock
shared.payoutTxId=Payout transaction ID shared.payoutTxId=Payout transaction ID

View File

@ -212,14 +212,14 @@ public class OfferDetailsWindow extends Overlay<OfferDetailsWindow> {
addConfirmationLabelLabel(gridPane, rowIndex, offerTypeLabel, addConfirmationLabelLabel(gridPane, rowIndex, offerTypeLabel,
DisplayUtils.getDirectionBothSides(direction), firstRowDistance); DisplayUtils.getDirectionBothSides(direction), firstRowDistance);
} }
String btcAmount = Res.get("shared.xmrAmount"); String amount = Res.get("shared.xmrAmount");
if (takeOfferHandlerOptional.isPresent()) { if (takeOfferHandlerOptional.isPresent()) {
addConfirmationLabelLabel(gridPane, ++rowIndex, btcAmount + xmrDirectionInfo, addConfirmationLabelLabel(gridPane, ++rowIndex, amount + xmrDirectionInfo,
HavenoUtils.formatXmr(tradeAmount, true)); HavenoUtils.formatXmr(tradeAmount, true));
addConfirmationLabelLabel(gridPane, ++rowIndex, VolumeUtil.formatVolumeLabel(currencyCode) + counterCurrencyDirectionInfo, addConfirmationLabelLabel(gridPane, ++rowIndex, VolumeUtil.formatVolumeLabel(currencyCode) + counterCurrencyDirectionInfo,
VolumeUtil.formatVolumeWithCode(offer.getVolumeByAmount(tradeAmount))); VolumeUtil.formatVolumeWithCode(offer.getVolumeByAmount(tradeAmount)));
} else { } else {
addConfirmationLabelLabel(gridPane, ++rowIndex, btcAmount + xmrDirectionInfo, addConfirmationLabelLabel(gridPane, ++rowIndex, amount + xmrDirectionInfo,
HavenoUtils.formatXmr(offer.getAmount(), true)); HavenoUtils.formatXmr(offer.getAmount(), true));
addConfirmationLabelLabel(gridPane, ++rowIndex, Res.get("offerDetailsWindow.minXmrAmount"), addConfirmationLabelLabel(gridPane, ++rowIndex, Res.get("offerDetailsWindow.minXmrAmount"),
HavenoUtils.formatXmr(offer.getMinAmount(), true)); HavenoUtils.formatXmr(offer.getMinAmount(), true));
@ -257,7 +257,8 @@ public class OfferDetailsWindow extends Overlay<OfferDetailsWindow> {
final String makerPaymentAccountId = offer.getMakerPaymentAccountId(); final String makerPaymentAccountId = offer.getMakerPaymentAccountId();
final PaymentAccount myPaymentAccount = user.getPaymentAccount(makerPaymentAccountId); final PaymentAccount myPaymentAccount = user.getPaymentAccount(makerPaymentAccountId);
String countryCode = offer.getCountryCode(); 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()); addConfirmationLabelLabel(gridPane, ++rowIndex, Res.get("offerDetailsWindow.myTradingAccount"), myPaymentAccount.getAccountName());
} else { } else {
final String method = Res.get(paymentMethod.getId()); final String method = Res.get(paymentMethod.getId());
@ -316,11 +317,16 @@ public class OfferDetailsWindow extends Overlay<OfferDetailsWindow> {
textArea.setEditable(false); textArea.setEditable(false);
} }
// get amount reserved for the offer
BigInteger reservedAmount = isMyOffer ? offer.getReservedAmount() : null;
rows = 3; rows = 3;
if (countryCode != null) if (countryCode != null)
rows++; rows++;
if (!isF2F) if (!isF2F)
rows++; rows++;
if (reservedAmount != null)
rows++;
addTitledGroupBg(gridPane, ++rowIndex, rows, Res.get("shared.details"), Layout.GROUP_DISTANCE); addTitledGroupBg(gridPane, ++rowIndex, rows, Res.get("shared.details"), Layout.GROUP_DISTANCE);
addConfirmationLabelTextFieldWithCopyIcon(gridPane, rowIndex, Res.get("shared.offerId"), offer.getId(), addConfirmationLabelTextFieldWithCopyIcon(gridPane, rowIndex, Res.get("shared.offerId"), offer.getId(),
@ -337,6 +343,9 @@ public class OfferDetailsWindow extends Overlay<OfferDetailsWindow> {
" " + " " +
HavenoUtils.formatXmr(offer.getOfferPayload().getMaxSellerSecurityDeposit(), true); HavenoUtils.formatXmr(offer.getOfferPayload().getMaxSellerSecurityDeposit(), true);
addConfirmationLabelLabel(gridPane, ++rowIndex, Res.get("shared.securityDeposit"), value); 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) if (countryCode != null && !isF2F)
addConfirmationLabelLabel(gridPane, ++rowIndex, Res.get("offerDetailsWindow.countryBank"), addConfirmationLabelLabel(gridPane, ++rowIndex, Res.get("offerDetailsWindow.countryBank"),