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
///////////////////////////////////////////////////////////////////////////////////////////
// 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() {

View file

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

View file

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

View file

@ -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<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) {
// fetch unspent, unfrozen, unlocked outputs

View file

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

View file

@ -212,14 +212,14 @@ public class OfferDetailsWindow extends Overlay<OfferDetailsWindow> {
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<OfferDetailsWindow> {
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<OfferDetailsWindow> {
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<OfferDetailsWindow> {
" " +
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"),