support funding make or take offer directly

QR code encodes payment URI
security deposit absorbs miner fee up to 5%
use binary search to maximize security deposit and minimize dust
show itemized funding popup on create offer
This commit is contained in:
woodser 2022-12-03 14:33:55 +00:00
parent 4dbbcd6217
commit dd0a307a84
44 changed files with 263 additions and 353 deletions

View file

@ -25,10 +25,10 @@ import bisq.core.offer.OpenOfferManager;
import bisq.core.support.dispute.Dispute;
import bisq.core.support.dispute.refund.RefundManager;
import bisq.core.trade.ClosedTradableManager;
import bisq.core.trade.HavenoUtils;
import bisq.core.trade.Trade;
import bisq.core.trade.TradeManager;
import bisq.core.trade.failed.FailedTradesManager;
import bisq.core.util.ParsingUtils;
import bisq.network.p2p.P2PService;
import java.math.BigInteger;
import java.util.List;
@ -137,7 +137,7 @@ public class Balances {
} else {
reservedAmt = trade.getContract().isMyRoleBuyer(tradeManager.getKeyRing().getPubKeyRing()) ? offerPayload.getBuyerSecurityDeposit() : offerPayload.getAmount() + offerPayload.getSellerSecurityDeposit();
}
sum = sum.add(Coin.valueOf(ParsingUtils.centinerosToAtomicUnits(reservedAmt).longValueExact()));
sum = sum.add(Coin.valueOf(HavenoUtils.centinerosToAtomicUnits(reservedAmt).longValueExact()));
}
reservedTradeBalance.set(sum);
}

View file

@ -17,16 +17,16 @@ import bisq.core.btc.setup.MoneroWalletRpcManager;
import bisq.core.btc.setup.WalletsSetup;
import bisq.core.offer.Offer;
import bisq.core.trade.MakerTrade;
import bisq.core.trade.SellerTrade;
import bisq.core.trade.Trade;
import bisq.core.trade.TradeManager;
import bisq.core.trade.BuyerTrade;
import bisq.core.trade.HavenoUtils;
import bisq.core.util.ParsingUtils;
import com.google.common.util.concurrent.Service.State;
import com.google.inject.name.Named;
import common.utils.JsonUtils;
import java.io.File;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.Arrays;
@ -83,8 +83,8 @@ public class XmrWalletService {
private static final String MONERO_WALLET_RPC_DEFAULT_PASSWORD = "password"; // only used if account password is null
private static final String MONERO_WALLET_NAME = "haveno_XMR";
private static final String MONERO_MULTISIG_WALLET_PREFIX = "xmr_multisig_trade_";
private static final int MINER_FEE_PADDING_MULTIPLIER = 2; // extra padding for miner fees = estimated fee * multiplier
private static final double MINER_FEE_TOLERANCE = 0.25; // miner fee must be within percent of estimated fee
public static final double MINER_FEE_TOLERANCE = 0.25; // miner fee must be within percent of estimated fee
private static final double SECURITY_DEPOSIT_TOLERANCE = Config.baseCurrencyNetwork() == BaseCurrencyNetwork.XMR_LOCAL ? 0.25 : 0.05; // security deposit absorbs miner fee up to percent
private final CoreAccountService accountService;
private final CoreMoneroConnectionsService connectionsService;
@ -265,106 +265,86 @@ public class XmrWalletService {
}
/**
* Create the reserve tx and freeze its inputs. The deposit amount is returned
* to the sender's payout address. Additional funds are reserved to allow
* fluctuations in the mining fee.
* Create the reserve tx and freeze its inputs. The full amount is returned
* to the sender's payout address less the trade fee.
*
* @param tradeFee - trade fee
* @param depositAmount - amount needed for the trade minus the trade fee
* @param returnAddress - return address for deposit amount
* @param addPadding - reserve additional padding to cover future mining fee
* @param returnAddress return address for reserved funds
* @param tradeFee trade fee
* @param peerAmount amount to give peer
* @param securityDeposit security deposit amount
* @return a transaction to reserve a trade
*/
public MoneroTxWallet createReserveTx(BigInteger tradeFee, String returnAddress, BigInteger depositAmount, boolean addPadding) {
MoneroWallet wallet = getWallet();
synchronized (wallet) {
// add miner fee padding to deposit amount
if (addPadding) {
// get estimated mining fee with deposit amount
MoneroTxWallet feeEstimateTx = wallet.createTx(new MoneroTxConfig()
.setAccountIndex(0)
.addDestination(HavenoUtils.getTradeFeeAddress(), tradeFee)
.addDestination(returnAddress, depositAmount));
BigInteger feeEstimate = feeEstimateTx.getFee();
BigInteger daemonFeeEstimate = getFeeEstimate(feeEstimateTx.getWeight());
log.info("createReserveTx() 1st feeEstimateTx with weight {} has fee {} versus daemon fee estimate of {} (diff={})", feeEstimateTx.getWeight(), feeEstimateTx.getFee(), daemonFeeEstimate, (feeEstimateTx.getFee().subtract(daemonFeeEstimate)));
// get estimated mining fee with deposit amount + previous estimated mining fee for better accuracy
feeEstimateTx = wallet.createTx(new MoneroTxConfig()
.setAccountIndex(0)
.addDestination(HavenoUtils.getTradeFeeAddress(), tradeFee)
.addDestination(returnAddress, depositAmount.add(feeEstimate.multiply(BigInteger.valueOf(MINER_FEE_PADDING_MULTIPLIER)))));
feeEstimate = feeEstimateTx.getFee();
log.info("createReserveTx() 2nd feeEstimateTx with weight {} has fee {} versus daemon fee estimate of {} (diff={})", feeEstimateTx.getWeight(), feeEstimateTx.getFee(), daemonFeeEstimate, (feeEstimateTx.getFee().subtract(daemonFeeEstimate)));
// add padding to deposit amount
BigInteger minerFeePadding = feeEstimate.multiply(BigInteger.valueOf(MINER_FEE_PADDING_MULTIPLIER));
depositAmount = depositAmount.add(minerFeePadding);
}
// create reserve tx
MoneroTxWallet reserveTx = wallet.createTx(new MoneroTxConfig()
.setAccountIndex(0)
.addDestination(HavenoUtils.getTradeFeeAddress(), tradeFee)
.addDestination(returnAddress, depositAmount));
log.info("Reserve tx weight={}, fee={}, depositAmount={}", reserveTx.getWeight(), reserveTx.getFee(), depositAmount);
// freeze inputs
for (MoneroOutput input : reserveTx.getInputs()) wallet.freezeOutput(input.getKeyImage().getHex());
wallet.save();
return reserveTx;
}
public MoneroTxWallet createReserveTx(BigInteger tradeFee, BigInteger peerAmount, BigInteger securityDeposit, String returnAddress) {
log.info("Creating reserve tx with fee={}, peerAmount={}, securityDeposit={}", tradeFee, peerAmount, securityDeposit);
return createTradeTx(tradeFee, peerAmount, securityDeposit, returnAddress);
}
/**
* Create the multisig deposit tx and freeze its inputs.
*
* @param trade the trade to create a deposit tx from
* @return MoneroTxWallet the multisig deposit tx
*/
public MoneroTxWallet createDepositTx(Trade trade) {
BigInteger tradeFee = ParsingUtils.coinToAtomicUnits(trade instanceof MakerTrade ? trade.getOffer().getMakerFee() : trade.getTakerFee());
Offer offer = trade.getProcessModel().getOffer();
BigInteger depositAmount = ParsingUtils.coinToAtomicUnits(trade instanceof SellerTrade ? offer.getAmount().add(offer.getSellerSecurityDeposit()) : offer.getBuyerSecurityDeposit());
String multisigAddress = trade.getProcessModel().getMultisigAddress();
BigInteger tradeFee = HavenoUtils.coinToAtomicUnits(trade instanceof MakerTrade ? trade.getOffer().getMakerFee() : trade.getTakerFee());
BigInteger peerAmount = HavenoUtils.coinToAtomicUnits(trade instanceof BuyerTrade ? Coin.ZERO : offer.getAmount());
BigInteger securityDeposit = HavenoUtils.coinToAtomicUnits(trade instanceof BuyerTrade ? offer.getBuyerSecurityDeposit() : offer.getSellerSecurityDeposit());
log.info("Creating deposit tx with fee={}, peerAmount={}, securityDeposit={}", tradeFee, peerAmount, securityDeposit);
return createTradeTx(tradeFee, peerAmount, securityDeposit, multisigAddress);
}
private MoneroTxWallet createTradeTx(BigInteger tradeFee, BigInteger peerAmount, BigInteger securityDeposit, String address) {
MoneroWallet wallet = getWallet();
synchronized (wallet) {
// create deposit tx
MoneroTxWallet depositTx = wallet.createTx(new MoneroTxConfig()
// binary search to maximize security deposit, thereby minimizing potential dust
MoneroTxWallet tradeTx = null;
double appliedTolerance = 0.0; // percent of tolerance to apply, thereby decreasing security deposit
double searchDiff = 1.0; // difference for next binary search
BigInteger maxAmount = peerAmount.add(securityDeposit);
for (int i = 0; i < 10; i++) {
try {
BigInteger amount = new BigDecimal(maxAmount).multiply(new BigDecimal(1.0 - SECURITY_DEPOSIT_TOLERANCE * appliedTolerance)).toBigInteger();
tradeTx = wallet.createTx(new MoneroTxConfig()
.setAccountIndex(0)
.addDestination(HavenoUtils.getTradeFeeAddress(), tradeFee)
.addDestination(multisigAddress, depositAmount));
.addDestination(address, amount));
appliedTolerance -= searchDiff; // apply less tolerance to increase security deposit
if (appliedTolerance < 0.0) break; // can send full security deposit
} catch (MoneroError e) {
appliedTolerance += searchDiff; // apply more tolerance to decrease security deposit
if (appliedTolerance > 1.0) throw e; // not enough money
}
searchDiff /= 2;
}
// freeze deposit inputs
for (MoneroOutput input : depositTx.getInputs()) wallet.freezeOutput(input.getKeyImage().getHex());
// freeze inputs
for (MoneroOutput input : tradeTx.getInputs()) wallet.freezeOutput(input.getKeyImage().getHex());
wallet.save();
return depositTx;
return tradeTx;
}
}
/**
* Verify a reserve or deposit transaction used during trading.
* Checks double spends, deposit amount and destination, trade fee, and mining fee.
* The transaction is submitted but not relayed to the pool then flushed.
* Verify a reserve or deposit transaction.
* Checks double spends, trade fee, deposit amount and destination, and miner fee.
* The transaction is submitted to the pool then flushed without relaying.
*
* @param depositAddress is the expected destination address for the deposit amount
* @param depositAmount is the expected amount deposited to multisig
* @param tradeFee is the expected fee for trading
* @param txHash is the transaction hash
* @param txHex is the transaction hex
* @param txKey is the transaction key
* @param keyImages are expected key images of inputs, ignored if null
* @param addPadding verifies depositAmount has additional padding to cover future mining fee
* @param tradeFee trade fee
* @param peerAmount amount to give peer
* @param securityDeposit security deposit amount
* @param address expected destination address for the deposit amount
* @param txHash transaction hash
* @param txHex transaction hex
* @param txKey transaction key
* @param keyImages expected key images of inputs, ignored if null
*/
public void verifyTradeTx(String depositAddress, BigInteger depositAmount, BigInteger tradeFee, String txHash, String txHex, String txKey, List<String> keyImages, boolean addPadding) {
public void verifyTradeTx(BigInteger tradeFee, BigInteger peerAmount, BigInteger securityDeposit, String address, String txHash, String txHex, String txKey, List<String> keyImages) {
MoneroDaemonRpc daemon = getDaemon();
MoneroWallet wallet = getWallet();
try {
log.info("Verifying trade tx with deposit amount={}", depositAmount);
// verify tx not submitted to pool
MoneroTx tx = daemon.getTx(txHash);
@ -375,14 +355,14 @@ public class XmrWalletService {
if (!result.isGood()) throw new RuntimeException("Failed to submit tx to daemon: " + JsonUtils.serialize(result));
tx = getTx(txHash);
// verify reserved key images
// verify key images
if (keyImages != null) {
Set<String> txKeyImages = new HashSet<String>();
for (MoneroOutput input : tx.getInputs()) txKeyImages.add(input.getKeyImage().getHex());
if (!txKeyImages.equals(new HashSet<String>(keyImages))) throw new Error("Reserve tx's inputs do not match claimed key images");
if (!txKeyImages.equals(new HashSet<String>(keyImages))) throw new Error("Tx inputs do not match claimed key images");
}
// verify the unlock height
// verify unlock height
if (tx.getUnlockHeight() != 0) throw new RuntimeException("Unlock height must be 0");
// verify trade fee
@ -391,22 +371,17 @@ public class XmrWalletService {
if (!check.isGood()) throw new RuntimeException("Invalid proof of trade fee");
if (!check.getReceivedAmount().equals(tradeFee)) throw new RuntimeException("Trade fee is incorrect amount, expected " + tradeFee + " but was " + check.getReceivedAmount());
// verify mining fee
// verify miner fee
BigInteger feeEstimate = getFeeEstimate(tx.getWeight());
double feeDiff = tx.getFee().subtract(feeEstimate).abs().doubleValue() / feeEstimate.doubleValue();
if (feeDiff > MINER_FEE_TOLERANCE) throw new Error("Mining fee is not within " + (MINER_FEE_TOLERANCE * 100) + "% of estimated fee, expected " + feeEstimate + " but was " + tx.getFee());
double feeDiff = tx.getFee().subtract(feeEstimate).abs().doubleValue() / feeEstimate.doubleValue(); // TODO: use BigDecimal?
if (feeDiff > MINER_FEE_TOLERANCE) throw new Error("Miner fee is not within " + (MINER_FEE_TOLERANCE * 100) + "% of estimated fee, expected " + feeEstimate + " but was " + tx.getFee());
log.info("Trade tx fee {} is within tolerance, diff%={}", tx.getFee(), feeDiff);
// verify deposit amount
check = wallet.checkTxKey(txHash, txKey, depositAddress);
check = wallet.checkTxKey(txHash, txKey, address);
if (!check.isGood()) throw new RuntimeException("Invalid proof of deposit amount");
if (addPadding) {
BigInteger minPadding = BigInteger.valueOf((long) (tx.getFee().multiply(BigInteger.valueOf(MINER_FEE_PADDING_MULTIPLIER)).doubleValue() * (1.0 - MINER_FEE_TOLERANCE)));
BigInteger actualPadding = check.getReceivedAmount().subtract(depositAmount);
if (actualPadding.compareTo(minPadding) < 0) throw new RuntimeException("Deposit amount is not enough, needed " + depositAmount.add(minPadding) + " (with padding) but was " + check.getReceivedAmount());
} else if (check.getReceivedAmount().compareTo(depositAmount) < 0) {
throw new RuntimeException("Deposit amount is not enough, needed " + depositAmount + " but was " + check.getReceivedAmount());
}
BigInteger minAmount = new BigDecimal(peerAmount.add(securityDeposit)).multiply(new BigDecimal(1.0 - SECURITY_DEPOSIT_TOLERANCE)).toBigInteger();
if (check.getReceivedAmount().compareTo(minAmount) < 0) throw new RuntimeException("Deposit amount is not enough, needed " + minAmount + " but was " + check.getReceivedAmount());
} finally {
try {
daemon.flushTxPool(txHash); // flush tx from pool
@ -915,7 +890,6 @@ public class XmrWalletService {
return getBalanceForSubaddress(wallet.getAddressIndex(address).getIndex());
}
// TODO: Coin represents centineros everywhere, but here it's atomic units. reconcile
public Coin getBalanceForSubaddress(int subaddressIndex) {
// get subaddress balance
@ -931,12 +905,11 @@ public class XmrWalletService {
// }
System.out.println("Returning balance for subaddress " + subaddressIndex + ": " + balance.longValueExact());
return Coin.valueOf(balance.longValueExact());
return HavenoUtils.atomicUnitsToCoin(balance);
}
public Coin getAvailableConfirmedBalance() {
return wallet != null ? Coin.valueOf(wallet.getUnlockedBalance(0).longValueExact()) : Coin.ZERO;
return wallet != null ? HavenoUtils.atomicUnitsToCoin(wallet.getUnlockedBalance(0)) : Coin.ZERO;
}
public Coin getSavingWalletBalance() {

View file

@ -42,7 +42,6 @@ import bisq.core.trade.statistics.TradeStatisticsManager;
import bisq.core.user.Preferences;
import bisq.core.user.User;
import bisq.core.util.JsonUtil;
import bisq.core.util.ParsingUtils;
import bisq.core.util.Validator;
import bisq.network.p2p.AckMessage;
@ -626,14 +625,15 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
ErrorMessageHandler errorMessageHandler) {
new Thread(() -> {
List<String> errorMessages = new ArrayList<String>();
for (OpenOffer scheduledOffer : openOffers.getObservableList()) {
for (OpenOffer scheduledOffer : new ArrayList<OpenOffer>(openOffers.getObservableList())) {
if (scheduledOffer.getState() != OpenOffer.State.SCHEDULED) continue;
CountDownLatch latch = new CountDownLatch(1);
processUnpostedOffer(scheduledOffer, (transaction) -> {
latch.countDown();
}, errorMessage -> {
latch.countDown();
onRemoved(scheduledOffer);
errorMessages.add(errorMessage);
latch.countDown();
});
HavenoUtils.awaitLatch(latch);
}
@ -655,7 +655,7 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
// get offer reserve amount
Coin offerReserveAmountCoin = openOffer.getOffer().getReserveAmount();
BigInteger offerReserveAmount = ParsingUtils.centinerosToAtomicUnits(offerReserveAmountCoin.value);
BigInteger offerReserveAmount = HavenoUtils.centinerosToAtomicUnits(offerReserveAmountCoin.value);
// handle sufficient available balance
if (xmrWalletService.getWallet().getUnlockedBalance(0).compareTo(offerReserveAmount) >= 0) {
@ -773,6 +773,7 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
// set offer state
openOffer.setState(OpenOffer.State.AVAILABLE);
requestPersistence();
resultHandler.handleResult(transaction);
if (!stopped) {
@ -832,17 +833,18 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
// verify maker's reserve tx (double spend, trade fee, trade amount, mining fee)
Offer offer = new Offer(request.getOfferPayload());
BigInteger tradeFee = ParsingUtils.coinToAtomicUnits(offer.getMakerFee());
BigInteger depositAmount = ParsingUtils.coinToAtomicUnits(offer.getDirection() == OfferDirection.BUY ? offer.getBuyerSecurityDeposit() : offer.getAmount().add(offer.getSellerSecurityDeposit()));
BigInteger tradeFee = HavenoUtils.coinToAtomicUnits(offer.getMakerFee());
BigInteger peerAmount = HavenoUtils.coinToAtomicUnits(offer.getDirection() == OfferDirection.BUY ? Coin.ZERO : offer.getAmount());
BigInteger securityDeposit = HavenoUtils.coinToAtomicUnits(offer.getDirection() == OfferDirection.BUY ? offer.getBuyerSecurityDeposit() : offer.getSellerSecurityDeposit());
xmrWalletService.verifyTradeTx(
request.getPayoutAddress(),
depositAmount,
tradeFee,
peerAmount,
securityDeposit,
request.getPayoutAddress(),
request.getReserveTxHash(),
request.getReserveTxHex(),
request.getReserveTxKey(),
request.getReserveTxKeyImages(),
true);
request.getReserveTxKeyImages());
// arbitrator signs offer to certify they have valid reserve tx
String offerPayloadAsJson = JsonUtil.objectToJson(request.getOfferPayload());

View file

@ -21,13 +21,17 @@ import bisq.common.taskrunner.Task;
import bisq.common.taskrunner.TaskRunner;
import bisq.core.btc.model.XmrAddressEntry;
import bisq.core.offer.Offer;
import bisq.core.offer.OfferDirection;
import bisq.core.offer.placeoffer.PlaceOfferModel;
import bisq.core.util.ParsingUtils;
import bisq.core.trade.HavenoUtils;
import lombok.extern.slf4j.Slf4j;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.List;
import org.bitcoinj.core.Coin;
import monero.daemon.model.MoneroOutput;
import monero.wallet.model.MoneroTxWallet;
@ -46,19 +50,18 @@ public class MakerReserveOfferFunds extends Task<PlaceOfferModel> {
try {
runInterceptHook();
// create reserve tx with padding
// create reserve tx
BigInteger makerFee = HavenoUtils.coinToAtomicUnits(offer.getMakerFee());
BigInteger peerAmount = HavenoUtils.coinToAtomicUnits(offer.getDirection() == OfferDirection.BUY ? Coin.ZERO : offer.getAmount());
BigInteger securityDeposit = HavenoUtils.coinToAtomicUnits(offer.getDirection() == OfferDirection.BUY ? offer.getBuyerSecurityDeposit() : offer.getSellerSecurityDeposit());
String returnAddress = model.getXmrWalletService().getOrCreateAddressEntry(offer.getId(), XmrAddressEntry.Context.TRADE_PAYOUT).getAddressString();
BigInteger makerFee = ParsingUtils.coinToAtomicUnits(offer.getMakerFee());
BigInteger depositAmount = ParsingUtils.coinToAtomicUnits(model.getReservedFundsForOffer());
log.info("Maker creating reserve tx with maker fee={} and depositAmount={}", makerFee, depositAmount);
MoneroTxWallet reserveTx = model.getXmrWalletService().createReserveTx(makerFee, returnAddress, depositAmount, true);
MoneroTxWallet reserveTx = model.getXmrWalletService().createReserveTx(makerFee, peerAmount, securityDeposit, returnAddress);
// collect reserved key images // TODO (woodser): switch to proof of reserve?
// collect reserved key images
List<String> reservedKeyImages = new ArrayList<String>();
for (MoneroOutput input : reserveTx.getInputs()) reservedKeyImages.add(input.getKeyImage().getHex());
// save offer state
// TODO (woodser): persist
model.setReserveTx(reserveTx);
offer.getOfferPayload().setReserveTxKeyImages(reservedKeyImages);
offer.setOfferFeePaymentTxId(reserveTx.getHash()); // TODO (woodser): don't use this field

View file

@ -68,7 +68,7 @@ import bisq.core.payment.UpiAccount;
import bisq.core.payment.VerseAccount;
import bisq.core.payment.WeChatPayAccount;
import bisq.core.payment.WesternUnionAccount;
import bisq.core.util.ParsingUtils;
import bisq.core.trade.HavenoUtils;
import bisq.core.locale.CurrencyUtil;
import bisq.core.locale.Res;
import bisq.core.locale.TradeCurrency;
@ -490,8 +490,8 @@ public final class PaymentMethod implements PersistablePayload, Comparable<Payme
// TODO: remove this when trade credits supported
boolean isFiat = CurrencyUtil.isFiatCurrency(currencyCode);
boolean isStagenet = Config.baseCurrencyNetwork() == BaseCurrencyNetwork.XMR_STAGENET;
if (isFiat && isStagenet && ParsingUtils.centinerosToXmr(riskBasedTradeLimit) > MAX_FIAT_STAGENET_XMR) {
riskBasedTradeLimit = ParsingUtils.xmrToCentineros(MAX_FIAT_STAGENET_XMR);
if (isFiat && isStagenet && HavenoUtils.centinerosToXmr(riskBasedTradeLimit) > MAX_FIAT_STAGENET_XMR) {
riskBasedTradeLimit = HavenoUtils.xmrToCentineros(MAX_FIAT_STAGENET_XMR);
}
return Coin.valueOf(riskBasedTradeLimit);
}

View file

@ -834,8 +834,8 @@ public abstract class DisputeManager<T extends DisputeList<Dispute>> extends Sup
(contract.isBuyerMakerAndSellerTaker() ? contract.getMakerPayoutAddressString() : contract.getTakerPayoutAddressString()) :
(contract.isBuyerMakerAndSellerTaker() ? contract.getTakerPayoutAddressString() : contract.getMakerPayoutAddressString());
String loserPayoutAddress = winnerPayoutAddress.equals(contract.getMakerPayoutAddressString()) ? contract.getTakerPayoutAddressString() : contract.getMakerPayoutAddressString();
BigInteger winnerPayoutAmount = ParsingUtils.coinToAtomicUnits(disputeResult.getWinner() == Winner.BUYER ? disputeResult.getBuyerPayoutAmount() : disputeResult.getSellerPayoutAmount());
BigInteger loserPayoutAmount = ParsingUtils.coinToAtomicUnits(disputeResult.getWinner() == Winner.BUYER ? disputeResult.getSellerPayoutAmount() : disputeResult.getBuyerPayoutAmount());
BigInteger winnerPayoutAmount = HavenoUtils.coinToAtomicUnits(disputeResult.getWinner() == Winner.BUYER ? disputeResult.getBuyerPayoutAmount() : disputeResult.getSellerPayoutAmount());
BigInteger loserPayoutAmount = HavenoUtils.coinToAtomicUnits(disputeResult.getWinner() == Winner.BUYER ? disputeResult.getSellerPayoutAmount() : disputeResult.getBuyerPayoutAmount());
// create transaction to get fee estimate
MoneroTxConfig txConfig = new MoneroTxConfig().setAccountIndex(0).setRelay(false);

View file

@ -35,9 +35,9 @@ import bisq.core.support.messages.ChatMessage;
import bisq.core.support.messages.SupportMessage;
import bisq.core.trade.ClosedTradableManager;
import bisq.core.trade.Contract;
import bisq.core.trade.HavenoUtils;
import bisq.core.trade.Trade;
import bisq.core.trade.TradeManager;
import bisq.core.util.ParsingUtils;
import bisq.network.p2p.AckMessageSourceType;
import bisq.network.p2p.NodeAddress;
@ -318,12 +318,12 @@ public final class ArbitrationManager extends DisputeManager<ArbitrationDisputeL
BigInteger destinationSum = (buyerPayoutDestination == null ? BigInteger.ZERO : buyerPayoutDestination.getAmount()).add(sellerPayoutDestination == null ? BigInteger.ZERO : sellerPayoutDestination.getAmount());
if (!arbitratorSignedPayoutTx.getOutputSum().equals(destinationSum.add(arbitratorSignedPayoutTx.getChangeAmount()))) throw new RuntimeException("Sum of outputs != destination amounts + change amount");
// TODO (woodser): verify fee is reasonable (e.g. within 2x of fee estimate tx)
// TODO: verify miner fee is within expected range
// verify winner and loser payout amounts
BigInteger txCost = arbitratorSignedPayoutTx.getFee().add(arbitratorSignedPayoutTx.getChangeAmount()); // fee + lost dust change
BigInteger expectedWinnerAmount = ParsingUtils.coinToAtomicUnits(disputeResult.getWinner() == Winner.BUYER ? disputeResult.getBuyerPayoutAmount() : disputeResult.getSellerPayoutAmount());
BigInteger expectedLoserAmount = ParsingUtils.coinToAtomicUnits(disputeResult.getWinner() == Winner.BUYER ? disputeResult.getSellerPayoutAmount() : disputeResult.getBuyerPayoutAmount());
BigInteger expectedWinnerAmount = HavenoUtils.coinToAtomicUnits(disputeResult.getWinner() == Winner.BUYER ? disputeResult.getBuyerPayoutAmount() : disputeResult.getSellerPayoutAmount());
BigInteger expectedLoserAmount = HavenoUtils.coinToAtomicUnits(disputeResult.getWinner() == Winner.BUYER ? disputeResult.getSellerPayoutAmount() : disputeResult.getBuyerPayoutAmount());
if (expectedLoserAmount.equals(BigInteger.ZERO)) expectedWinnerAmount = expectedWinnerAmount.subtract(txCost); // winner only pays tx cost if loser gets 0
else expectedLoserAmount = expectedLoserAmount.subtract(txCost); // loser pays tx cost
BigInteger actualWinnerAmount = disputeResult.getWinner() == Winner.BUYER ? buyerPayoutDestination.getAmount() : sellerPayoutDestination.getAmount();

View file

@ -29,6 +29,8 @@ import bisq.core.trade.messages.PaymentSentMessage;
import bisq.core.util.JsonUtil;
import lombok.extern.slf4j.Slf4j;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.net.URI;
import java.util.Collection;
import java.util.concurrent.CountDownLatch;
@ -36,6 +38,8 @@ import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import org.bitcoinj.core.Coin;
import com.google.common.base.CaseFormat;
import com.google.common.base.Charsets;
@ -48,6 +52,50 @@ public class HavenoUtils {
public static final String LOOPBACK_HOST = "127.0.0.1"; // local loopback address to host Monero node
public static final String LOCALHOST = "localhost";
// multipliers to convert units
private static BigInteger CENTINEROS_AU_MULTIPLIER = new BigInteger("10000");
private static BigInteger XMR_AU_MULTIPLIER = new BigInteger("1000000000000");
public static BigInteger coinToAtomicUnits(Coin coin) {
return centinerosToAtomicUnits(coin.value);
}
public static double coinToXmr(Coin coin) {
return atomicUnitsToXmr(coinToAtomicUnits(coin));
}
public static BigInteger centinerosToAtomicUnits(long centineros) {
return BigInteger.valueOf(centineros).multiply(CENTINEROS_AU_MULTIPLIER);
}
public static double centinerosToXmr(long centineros) {
return atomicUnitsToXmr(centinerosToAtomicUnits(centineros));
}
public static long atomicUnitsToCentineros(long atomicUnits) { // TODO: atomic units should be BigInteger; remove this?
return atomicUnits / CENTINEROS_AU_MULTIPLIER.longValue();
}
public static long atomicUnitsToCentineros(BigInteger atomicUnits) {
return atomicUnits.divide(CENTINEROS_AU_MULTIPLIER).longValueExact();
}
public static Coin atomicUnitsToCoin(BigInteger atomicUnits) {
return Coin.valueOf(atomicUnitsToCentineros(atomicUnits));
}
public static double atomicUnitsToXmr(BigInteger atomicUnits) {
return new BigDecimal(atomicUnits).divide(new BigDecimal(XMR_AU_MULTIPLIER)).doubleValue();
}
public static BigInteger xmrToAtomicUnits(double xmr) {
return BigDecimal.valueOf(xmr).multiply(new BigDecimal(XMR_AU_MULTIPLIER)).toBigInteger();
}
public static long xmrToCentineros(double xmr) {
return atomicUnitsToCentineros(xmrToAtomicUnits(xmr));
}
/**
* Get address to collect trade fees.
*

View file

@ -710,7 +710,7 @@ public abstract class Trade implements Tradable, Model {
Preconditions.checkNotNull(buyerPayoutAddress, "Buyer payout address must not be null");
BigInteger sellerDepositAmount = multisigWallet.getTx(this.getSeller().getDepositTxHash()).getIncomingAmount();
BigInteger buyerDepositAmount = multisigWallet.getTx(this.getBuyer().getDepositTxHash()).getIncomingAmount();
BigInteger tradeAmount = ParsingUtils.coinToAtomicUnits(this.getAmount());
BigInteger tradeAmount = HavenoUtils.coinToAtomicUnits(this.getAmount());
BigInteger buyerPayoutAmount = buyerDepositAmount.add(tradeAmount);
BigInteger sellerPayoutAmount = sellerDepositAmount.subtract(tradeAmount);
@ -763,7 +763,7 @@ public abstract class Trade implements Tradable, Model {
Contract contract = getContract();
BigInteger sellerDepositAmount = multisigWallet.getTx(getSeller().getDepositTxHash()).getIncomingAmount(); // TODO (woodser): redundancy of processModel.getPreparedDepositTxId() vs this.getDepositTxId() necessary or avoidable?
BigInteger buyerDepositAmount = multisigWallet.getTx(getBuyer().getDepositTxHash()).getIncomingAmount();
BigInteger tradeAmount = ParsingUtils.coinToAtomicUnits(getAmount());
BigInteger tradeAmount = HavenoUtils.coinToAtomicUnits(getAmount());
// describe payout tx
MoneroTxSet describedTxSet = multisigWallet.describeTxSet(new MoneroTxSet().setMultisigTxHex(payoutTxHex));

View file

@ -24,11 +24,11 @@ import bisq.common.crypto.Sig;
import bisq.common.taskrunner.TaskRunner;
import bisq.core.offer.Offer;
import bisq.core.offer.OfferDirection;
import bisq.core.trade.HavenoUtils;
import bisq.core.trade.Trade;
import bisq.core.trade.messages.DepositRequest;
import bisq.core.trade.messages.DepositResponse;
import bisq.core.trade.protocol.TradingPeer;
import bisq.core.util.ParsingUtils;
import bisq.network.p2p.NodeAddress;
import bisq.network.p2p.SendDirectMessageListener;
import common.utils.JsonUtils;
@ -37,6 +37,9 @@ import java.math.BigInteger;
import java.util.Arrays;
import java.util.Date;
import java.util.UUID;
import org.bitcoinj.core.Coin;
import lombok.extern.slf4j.Slf4j;
import monero.daemon.MoneroDaemon;
import monero.daemon.model.MoneroSubmitTxResult;
@ -70,28 +73,30 @@ public class ArbitratorProcessDepositRequest extends TradeTask {
// set peer's signature
peer.setContractSignature(signature);
// collect expected values of deposit tx
// collect expected values
Offer offer = trade.getOffer();
boolean isFromTaker = request.getSenderNodeAddress().equals(trade.getTaker().getNodeAddress());
boolean isFromBuyer = isFromTaker ? offer.getDirection() == OfferDirection.SELL : offer.getDirection() == OfferDirection.BUY;
BigInteger depositAmount = ParsingUtils.coinToAtomicUnits(isFromBuyer ? offer.getBuyerSecurityDeposit() : offer.getAmount().add(offer.getSellerSecurityDeposit()));
BigInteger peerAmount = HavenoUtils.coinToAtomicUnits(isFromBuyer ? Coin.ZERO : offer.getAmount());
BigInteger securityDeposit = HavenoUtils.coinToAtomicUnits(isFromBuyer ? offer.getBuyerSecurityDeposit() : offer.getSellerSecurityDeposit());
String depositAddress = processModel.getMultisigAddress();
BigInteger tradeFee;
TradingPeer trader = trade.getTradingPeer(request.getSenderNodeAddress());
if (trader == processModel.getMaker()) tradeFee = ParsingUtils.coinToAtomicUnits(trade.getOffer().getMakerFee());
else if (trader == processModel.getTaker()) tradeFee = ParsingUtils.coinToAtomicUnits(trade.getTakerFee());
if (trader == processModel.getMaker()) tradeFee = HavenoUtils.coinToAtomicUnits(trade.getOffer().getMakerFee());
else if (trader == processModel.getTaker()) tradeFee = HavenoUtils.coinToAtomicUnits(trade.getTakerFee());
else throw new RuntimeException("DepositRequest is not from maker or taker");
// verify deposit tx
try {
trade.getXmrWalletService().verifyTradeTx(depositAddress,
depositAmount,
trade.getXmrWalletService().verifyTradeTx(
tradeFee,
peerAmount,
securityDeposit,
depositAddress,
trader.getDepositTxHash(),
request.getDepositTxHex(),
request.getDepositTxKey(),
null,
false);
null);
} catch (Exception e) {
throw new RuntimeException("Error processing deposit tx from " + (isFromTaker ? "taker " : "maker ") + request.getSenderNodeAddress() + ", offerId=" + offer.getId() + ": " + e.getMessage());
}

View file

@ -20,11 +20,14 @@ package bisq.core.trade.protocol.tasks;
import bisq.common.taskrunner.TaskRunner;
import bisq.core.offer.Offer;
import bisq.core.offer.OfferDirection;
import bisq.core.trade.HavenoUtils;
import bisq.core.trade.Trade;
import bisq.core.trade.messages.InitTradeRequest;
import bisq.core.trade.protocol.TradingPeer;
import bisq.core.util.ParsingUtils;
import java.math.BigInteger;
import org.bitcoinj.core.Coin;
import lombok.extern.slf4j.Slf4j;
/**
@ -52,18 +55,19 @@ public class ArbitratorProcessReserveTx extends TradeTask {
// TODO (woodser): if signer online, should never be called by maker
// process reserve tx with expected terms
BigInteger tradeFee = ParsingUtils.coinToAtomicUnits(isFromTaker ? trade.getTakerFee() : offer.getMakerFee());
BigInteger depositAmount = ParsingUtils.coinToAtomicUnits(isFromBuyer ? offer.getBuyerSecurityDeposit() : offer.getAmount().add(offer.getSellerSecurityDeposit()));
BigInteger tradeFee = HavenoUtils.coinToAtomicUnits(isFromTaker ? trade.getTakerFee() : offer.getMakerFee());
BigInteger peerAmount = HavenoUtils.coinToAtomicUnits(isFromBuyer ? Coin.ZERO : offer.getAmount());
BigInteger securityDeposit = HavenoUtils.coinToAtomicUnits(isFromBuyer ? offer.getBuyerSecurityDeposit() : offer.getSellerSecurityDeposit());
try {
trade.getXmrWalletService().verifyTradeTx(
request.getPayoutAddress(),
depositAmount,
tradeFee,
peerAmount,
securityDeposit,
request.getPayoutAddress(),
request.getReserveTxHash(),
request.getReserveTxHex(),
request.getReserveTxKey(),
null,
true);
null);
} catch (Exception e) {
throw new RuntimeException("Error processing reserve tx from " + (isFromTaker ? "taker " : "maker ") + request.getSenderNodeAddress() + ", offerId=" + offer.getId() + ": " + e.getMessage());
}

View file

@ -19,11 +19,15 @@ package bisq.core.trade.protocol.tasks;
import bisq.common.taskrunner.TaskRunner;
import bisq.core.btc.model.XmrAddressEntry;
import bisq.core.offer.OfferDirection;
import bisq.core.trade.HavenoUtils;
import bisq.core.trade.Trade;
import bisq.core.util.ParsingUtils;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.List;
import org.bitcoinj.core.Coin;
import monero.daemon.model.MoneroOutput;
import monero.wallet.model.MoneroTxWallet;
@ -38,11 +42,12 @@ public class TakerReserveTradeFunds extends TradeTask {
try {
runInterceptHook();
// create reserve tx without padding
// create reserve tx
BigInteger takerFee = HavenoUtils.coinToAtomicUnits(trade.getTakerFee());
BigInteger peerAmount = HavenoUtils.coinToAtomicUnits(trade.getOffer().getDirection() == OfferDirection.BUY ? trade.getOffer().getAmount() : Coin.ZERO);
BigInteger securityDeposit = HavenoUtils.coinToAtomicUnits(trade.getOffer().getDirection() == OfferDirection.BUY ? trade.getOffer().getSellerSecurityDeposit() : trade.getOffer().getBuyerSecurityDeposit());
String returnAddress = model.getXmrWalletService().getOrCreateAddressEntry(trade.getOffer().getId(), XmrAddressEntry.Context.TRADE_PAYOUT).getAddressString();
BigInteger takerFee = ParsingUtils.coinToAtomicUnits(trade.getTakerFee());
BigInteger depositAmount = ParsingUtils.centinerosToAtomicUnits(processModel.getFundsNeededForTradeAsLong());
MoneroTxWallet reserveTx = model.getXmrWalletService().createReserveTx(takerFee, returnAddress, depositAmount, true);
MoneroTxWallet reserveTx = model.getXmrWalletService().createReserveTx(takerFee, peerAmount, securityDeposit, returnAddress);
// collect reserved key images
List<String> reservedKeyImages = new ArrayList<String>();
@ -51,7 +56,7 @@ public class TakerReserveTradeFunds extends TradeTask {
// save process state
processModel.setReserveTx(reserveTx);
processModel.getTaker().setReserveTxKeyImages(reservedKeyImages);
processModel.getTradeManager().requestPersistence();
complete();
} catch (Throwable t) {
trade.setErrorMessage("An error occurred.\n" +

View file

@ -9,58 +9,12 @@ import org.bitcoinj.core.Coin;
import org.bitcoinj.utils.MonetaryFormat;
import org.apache.commons.lang3.StringUtils;
import java.math.BigDecimal;
import java.math.BigInteger;
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class ParsingUtils {
// multipliers to convert units
private static BigInteger CENTINEROS_AU_MULTIPLIER = new BigInteger("10000");
private static BigInteger XMR_AU_MULTIPLIER = new BigInteger("1000000000000");
public static BigInteger coinToAtomicUnits(Coin coin) {
return centinerosToAtomicUnits(coin.value);
}
public static double coinToXmr(Coin coin) {
return atomicUnitsToXmr(coinToAtomicUnits(coin));
}
public static BigInteger centinerosToAtomicUnits(long centineros) {
return BigInteger.valueOf(centineros).multiply(ParsingUtils.CENTINEROS_AU_MULTIPLIER);
}
public static double centinerosToXmr(long centineros) {
return atomicUnitsToXmr(centinerosToAtomicUnits(centineros));
}
public static long atomicUnitsToCentineros(long atomicUnits) { // TODO: atomic units should be BigInteger; remove this?
return atomicUnits / CENTINEROS_AU_MULTIPLIER.longValue();
}
public static long atomicUnitsToCentineros(BigInteger atomicUnits) {
return atomicUnits.divide(CENTINEROS_AU_MULTIPLIER).longValueExact();
}
public static Coin atomicUnitsToCoin(BigInteger atomicUnits) {
return Coin.valueOf(atomicUnitsToCentineros(atomicUnits));
}
public static double atomicUnitsToXmr(BigInteger atomicUnits) {
return new BigDecimal(atomicUnits).divide(new BigDecimal(XMR_AU_MULTIPLIER)).doubleValue();
}
public static BigInteger xmrToAtomicUnits(double xmr) {
return BigDecimal.valueOf(xmr).multiply(new BigDecimal(XMR_AU_MULTIPLIER)).toBigInteger();
}
public static long xmrToCentineros(double xmr) {
return atomicUnitsToCentineros(xmrToAtomicUnits(xmr));
}
public static Coin parseToCoin(String input, CoinFormatter coinFormatter) {
return parseToCoin(input, coinFormatter.getMonetaryFormat());
}

View file

@ -451,7 +451,7 @@ createOffer.fundsBox.offerFee=Trade fee
createOffer.fundsBox.networkFee=Mining fee
createOffer.fundsBox.placeOfferSpinnerInfo=Offer publishing is in progress ...
createOffer.fundsBox.paymentLabel=Haveno trade with ID {0}
createOffer.fundsBox.fundsStructure=({0} security deposit, {1} trade fee, {2} mining fee)
createOffer.fundsBox.fundsStructure=({0} security deposit, {1} trade fee)
createOffer.success.headline=Your offer has been published
createOffer.success.info=You can manage your open offers at \"Portfolio/My open offers\".
createOffer.info.sellAtMarketPrice=You will always sell at market price as the price of your offer will be continuously updated.
@ -477,13 +477,11 @@ createOffer.createOfferFundWalletInfo.headline=Fund your offer
# suppress inspection "TrailingSpacesInProperty"
createOffer.createOfferFundWalletInfo.tradeAmount=- Trade amount: {0} \n
createOffer.createOfferFundWalletInfo.msg=You need to deposit {0} to this offer.\n\n\
Those funds are reserved in your local wallet and will get locked into the multisig deposit address once someone takes your offer.\n\n\
Those funds are reserved in your local wallet and will get locked into the multisig wallet once someone takes your offer.\n\n\
The amount is the sum of:\n\
{1}\
- Your security deposit: {2}\n\
- Trading fee: {3}\n\
- Mining fee: {4}\n\n\
You can choose between two options when funding your trade:\n- Use your Haveno wallet (convenient, but transactions may be linkable) OR\n- Transfer from an external wallet (potentially more private)\n\nYou will see all funding options and details after closing this popup.
- Trading fee: {3}
# only first part "An error occurred when placing the offer:" has been used before. We added now the rest (need update in existing translations!)
createOffer.amountPriceBox.error.message=An error occurred when placing the offer:\n\n{0}\n\n\
@ -533,7 +531,7 @@ takeOffer.fundsBox.offerFee=Trade fee
takeOffer.fundsBox.networkFee=Total mining fees
takeOffer.fundsBox.takeOfferSpinnerInfo=Take offer in progress ...
takeOffer.fundsBox.paymentLabel=Haveno trade with ID {0}
takeOffer.fundsBox.fundsStructure=({0} security deposit, {1} trade fee, {2} mining fee)
takeOffer.fundsBox.fundsStructure=({0} security deposit, {1} trade fee)
takeOffer.success.headline=You have successfully taken an offer.
takeOffer.success.info=You can see the status of your trade at \"Portfolio/Open trades\".
takeOffer.error.message=An error occurred when taking the offer.\n\n{0}
@ -545,7 +543,7 @@ takeOffer.noPriceFeedAvailable=You cannot take that offer as it uses a percentag
takeOffer.takeOfferFundWalletInfo.headline=Fund your trade
# suppress inspection "TrailingSpacesInProperty"
takeOffer.takeOfferFundWalletInfo.tradeAmount=- Trade amount: {0} \n
takeOffer.takeOfferFundWalletInfo.msg=You need to deposit {0} for taking this offer.\n\nThe amount is the sum of:\n{1}- Your security deposit: {2}\n- Trading fee: {3}\n- Total mining fees: {4}\n\nYou can choose between two options when funding your trade:\n- Use your Haveno wallet (convenient, but transactions may be linkable) OR\n- Transfer from an external wallet (potentially more private)\n\nYou will see all funding options and details after closing this popup.
takeOffer.takeOfferFundWalletInfo.msg=You need to deposit {0} for taking this offer.\n\nThe amount is the sum of:\n{1}- Your security deposit: {2}\n- Trading fee: {3}
takeOffer.alreadyPaidInFunds=If you have already paid in funds you can withdraw it in the \"Funds/Send funds\" screen.
takeOffer.setAmountPrice=Set amount
takeOffer.alreadyFunded.askCancel=You have already funded that offer.\nIf you cancel now, your funds will be moved to your local Haveno wallet and are available for withdrawal in the \"Funds/Send funds\" screen.\nAre you sure you want to cancel?
@ -2242,9 +2240,6 @@ systemTray.tooltip=Haveno: A decentralized bitcoin exchange network
# GUI Util
####################################################################
guiUtil.miningFeeInfo=Please be sure that the mining fee used by your external wallet is \
at least {0} satoshis/vbyte. Otherwise, the trade transactions may not be confirmed in time and the trade will end up in a dispute.
guiUtil.accountExport.savedToPath=Trading accounts saved to path:\n{0}
guiUtil.accountExport.noAccountSetup=You don't have trading accounts set up for exporting.
guiUtil.accountExport.selectPath=Select path to {0}

View file

@ -1710,8 +1710,6 @@ systemTray.tooltip=Bisq: Decentralizovaná směnárna bitcoinů
# GUI Util
####################################################################
guiUtil.miningFeeInfo=Ujistěte se, že poplatek za těžbu používaný vaší externí peněženkou je alespoň {0} satoshi/vbyte. Jinak nemusí být obchodní transakce potvrzeny včas a obchod skončí sporem.
guiUtil.accountExport.savedToPath=Obchodní účty uložené na:\n{0}
guiUtil.accountExport.noAccountSetup=Nemáte nastaveny obchodní účty pro export.
guiUtil.accountExport.selectPath=Vyberte cestu k {0}

View file

@ -1710,8 +1710,6 @@ systemTray.tooltip=Bisq: Ein dezentrales Bitcoin-Tauschbörsen-Netzwerk
# GUI Util
####################################################################
guiUtil.miningFeeInfo=Bitte stellen Sie sicher, dass die Mining-Gebühr für Ihre externe Wallet mindestens {0} satoshis/byte ist. Ansonsten wird die Transaktion des Trades nicht rechtzeitig bestätigt und der Trade wird in einem Konflikt enden.
guiUtil.accountExport.savedToPath=Handelskonten in Verzeichnis gespeichert:\n{0}
guiUtil.accountExport.noAccountSetup=Sie haben kein Handelskonto zum Exportieren eingerichtet.
guiUtil.accountExport.selectPath=Verzeichnis auswählen zum {0}

View file

@ -1710,8 +1710,6 @@ systemTray.tooltip=Bisq: Una red de intercambio de bitcoin descentralizada
# GUI Util
####################################################################
guiUtil.miningFeeInfo=Por favor asegúrese de que la comisión de minado usada en su monedero externo es de al menos {0} sat/vbyte. De lo contrario, las transacciones de intercambio podrían no confirmarse y el intercambio acabaría en disputa.
guiUtil.accountExport.savedToPath=Las cuentas de intercambio se han guardado en el directorio:\n{0}
guiUtil.accountExport.noAccountSetup=No tiene cuentas de intercambio configuradas para exportar.
guiUtil.accountExport.selectPath=Seleccionar directorio a {0}

View file

@ -1710,8 +1710,6 @@ systemTray.tooltip=Bisq: A decentralized bitcoin exchange network
# GUI Util
####################################################################
guiUtil.miningFeeInfo=Please be sure that the mining fee used by your external wallet is at least {0} satoshis/vbyte. Otherwise the trade transactions may not be confirmed in time and the trade will end up in a dispute.
guiUtil.accountExport.savedToPath=حساب های معاملاتی در مسیر ذیل ذخیره شد:\n{0}
guiUtil.accountExport.noAccountSetup=شما حساب های معاملاتی برای صادرات ندارید.
guiUtil.accountExport.selectPath=انتخاب مسیر به {0}

View file

@ -1711,8 +1711,6 @@ systemTray.tooltip=Bisq: Une plateforme d''échange décentralisée sur le rése
# GUI Util
####################################################################
guiUtil.miningFeeInfo=Veuillez vous assurer que les frais de minage utilisés par votre portefeuille externe sont d'au moins {0} satoshis/vbyte. Le cas échéant les transactions de trade pourraient ne peut être confirmée à temps et le trade aboutirait à une dispute.
guiUtil.accountExport.savedToPath=Les comptes de trading sont sauvegardés vers l''arborescence:\n{0}
guiUtil.accountExport.noAccountSetup=Vous n'avez pas de comptes de trading configurés pour exportation.
guiUtil.accountExport.selectPath=Sélectionner l''arborescence vers {0}

View file

@ -1710,8 +1710,6 @@ systemTray.tooltip=Bisq: una rete di scambio decentralizzata di bitcoin
# GUI Util
####################################################################
guiUtil.miningFeeInfo=Please be sure that the mining fee used by your external wallet is at least {0} satoshis/vbyte. Otherwise the trade transactions may not be confirmed in time and the trade will end up in a dispute.
guiUtil.accountExport.savedToPath=Account di trading salvati nel percorso:\n{0}
guiUtil.accountExport.noAccountSetup=Non hai account di trading impostati per l'esportazione.
guiUtil.accountExport.selectPath=Seleziona il percorso per {0}

View file

@ -1710,8 +1710,6 @@ systemTray.tooltip=Bisq: 分散的ビットコイン取引ネットワーク
# GUI Util
####################################################################
guiUtil.miningFeeInfo=外部ウォレットで使用されるマイニング手数料が少なくとも{0} satoshis/vbyte あることを確認してください。 そうでなければ、このトレードトランザクションは承認されない可能性、トレードは係争に終わる可能性があります。
guiUtil.accountExport.savedToPath=取引アカウントを下記パスに保存しました:\n{0}
guiUtil.accountExport.noAccountSetup=取引アカウントのエクスポート設定がされていません
guiUtil.accountExport.selectPath={0}のパスを選択

View file

@ -1718,8 +1718,6 @@ systemTray.tooltip=Bisq: a rede de exchange decentralizada de bitcoin
# GUI Util
####################################################################
guiUtil.miningFeeInfo=Please be sure that the mining fee used by your external wallet is at least {0} satoshis/vbyte. Otherwise the trade transactions may not be confirmed in time and the trade will end up in a dispute.
guiUtil.accountExport.savedToPath=Contas de negociação salvas na pasta:\n{0}
guiUtil.accountExport.noAccountSetup=Você não tem contas de negociação para exportar.
guiUtil.accountExport.selectPath=Selecione pasta de {0}

View file

@ -1708,8 +1708,6 @@ systemTray.tooltip=Bisq: Uma rede de echange de bitcoin descentralizada
# GUI Util
####################################################################
guiUtil.miningFeeInfo=Please be sure that the mining fee used by your external wallet is at least {0} satoshis/vbyte. Otherwise the trade transactions may not be confirmed in time and the trade will end up in a dispute.
guiUtil.accountExport.savedToPath=Contas de negociação guardadas em:\n{0}
guiUtil.accountExport.noAccountSetup=Você não tem contas de negociação prontas para exportar.
guiUtil.accountExport.selectPath=Selecione diretório de {0}

View file

@ -1710,8 +1710,6 @@ systemTray.tooltip=Bisq: A decentralized bitcoin exchange network
# GUI Util
####################################################################
guiUtil.miningFeeInfo=Please be sure that the mining fee used by your external wallet is at least {0} satoshis/vbyte. Otherwise the trade transactions may not be confirmed in time and the trade will end up in a dispute.
guiUtil.accountExport.savedToPath=Торговые счета в:\n{0}
guiUtil.accountExport.noAccountSetup=У вас нет торговых счетов для экспортирования.
guiUtil.accountExport.selectPath=Выбрать путь к {0}

View file

@ -1710,8 +1710,6 @@ systemTray.tooltip=Bisq: A decentralized bitcoin exchange network
# GUI Util
####################################################################
guiUtil.miningFeeInfo=Please be sure that the mining fee used by your external wallet is at least {0} satoshis/vbyte. Otherwise the trade transactions may not be confirmed in time and the trade will end up in a dispute.
guiUtil.accountExport.savedToPath=บัญชีการค้าที่บันทึกไว้ในเส้นทาง: \n{0}
guiUtil.accountExport.noAccountSetup=คุณไม่มีบัญชีการซื้อขายที่ตั้งค่าไว้สำหรับการส่งออก
guiUtil.accountExport.selectPath=เลือกเส้นทางไปที่ {0}

View file

@ -1712,8 +1712,6 @@ systemTray.tooltip=Bisq: A decentralized bitcoin exchange network
# GUI Util
####################################################################
guiUtil.miningFeeInfo=Please be sure that the mining fee used by your external wallet is at least {0} satoshis/vbyte. Otherwise the trade transactions may not be confirmed in time and the trade will end up in a dispute.
guiUtil.accountExport.savedToPath=tài khoản giao dịch được lưu vào đường dẫn:\n{0}
guiUtil.accountExport.noAccountSetup=Bạn không có tài khoản giao dịch được thiết lập để truy xuất.
guiUtil.accountExport.selectPath=Chọn đường dẫn đến {0}

View file

@ -1714,8 +1714,6 @@ systemTray.tooltip=Bisq去中心化比特币交易网络
# GUI Util
####################################################################
guiUtil.miningFeeInfo=请确保您的外部钱包使用的矿工手续费费用足够高至少为 {0} 聪/字节,以便矿工接受资金交易。\n否则交易所交易无法确认交易最终将会出现纠纷。
guiUtil.accountExport.savedToPath=交易账户保存在路径:\n{0}
guiUtil.accountExport.noAccountSetup=您没有交易账户设置导出。
guiUtil.accountExport.selectPath=选择路径 {0}

View file

@ -1710,8 +1710,6 @@ systemTray.tooltip=Bisq去中心化比特幣交易網絡
# GUI Util
####################################################################
guiUtil.miningFeeInfo=請確保您的外部錢包使用的礦工手續費費用足夠高至少為 {0} 聰/字節,以便礦工接受資金交易。\n否則交易所交易無法確認交易最終將會出現糾紛。
guiUtil.accountExport.savedToPath=交易賬户保存在路徑:\n{0}
guiUtil.accountExport.noAccountSetup=您沒有交易賬户設置導出。
guiUtil.accountExport.selectPath=選擇路徑 {0}

View file

@ -3,7 +3,7 @@ package bisq.daemon.grpc;
import bisq.core.api.CoreApi;
import bisq.core.support.dispute.Attachment;
import bisq.core.support.dispute.DisputeResult;
import bisq.core.util.ParsingUtils;
import bisq.core.trade.HavenoUtils;
import bisq.common.proto.ProtoUtil;
@ -109,7 +109,7 @@ public class GrpcDisputesService extends DisputesImplBase {
var winner = ProtoUtil.enumFromProto(DisputeResult.Winner.class, req.getWinner().name());
var reason = ProtoUtil.enumFromProto(DisputeResult.Reason.class, req.getReason().name());
// scale atomic unit to centineros for consistency TODO switch base to atomic units?
var customPayoutAmount = ParsingUtils.atomicUnitsToCentineros(req.getCustomPayoutAmount());
var customPayoutAmount = HavenoUtils.atomicUnitsToCentineros(req.getCustomPayoutAmount());
coreApi.resolveDispute(req.getTradeId(), winner, reason, req.getSummaryNotes(), customPayoutAmount);
var reply = ResolveDisputeReply.newBuilder().build();
responseObserver.onNext(reply);

View file

@ -21,7 +21,7 @@ import bisq.core.api.CoreApi;
import bisq.core.api.model.OfferInfo;
import bisq.core.offer.Offer;
import bisq.core.offer.OpenOffer;
import bisq.core.util.ParsingUtils;
import bisq.core.trade.HavenoUtils;
import bisq.proto.grpc.CancelOfferReply;
import bisq.proto.grpc.CancelOfferRequest;
import bisq.proto.grpc.PostOfferReply;
@ -154,8 +154,8 @@ class GrpcOffersService extends OffersImplBase {
req.getPrice(),
req.getUseMarketBasedPrice(),
req.getMarketPriceMarginPct(),
ParsingUtils.atomicUnitsToCentineros(req.getAmount()), // scale atomic unit to centineros for consistency TODO switch base to atomic units?
ParsingUtils.atomicUnitsToCentineros(req.getMinAmount()),
HavenoUtils.atomicUnitsToCentineros(req.getAmount()), // scale atomic unit to centineros for consistency TODO switch base to atomic units?
HavenoUtils.atomicUnitsToCentineros(req.getMinAmount()),
req.getBuyerSecurityDepositPct(),
req.getTriggerPrice(),
req.getPaymentAccountId(),

View file

@ -71,8 +71,7 @@ public class AddressTextField extends AnchorPane {
textField.setOnMousePressed(event -> wasPrimaryButtonDown = event.isPrimaryButtonDown());
textField.setOnMouseReleased(event -> {
if (wasPrimaryButtonDown)
GUIUtil.showFeeInfoBeforeExecute(AddressTextField.this::openWallet);
if (wasPrimaryButtonDown) openWallet();
wasPrimaryButtonDown = false;
});
@ -83,17 +82,17 @@ public class AddressTextField extends AnchorPane {
extWalletIcon.getStyleClass().addAll("icon", "highlight");
extWalletIcon.setTooltip(new Tooltip(tooltipText));
AwesomeDude.setIcon(extWalletIcon, AwesomeIcon.SIGNIN);
extWalletIcon.setOnMouseClicked(e -> GUIUtil.showFeeInfoBeforeExecute(this::openWallet));
extWalletIcon.setOnMouseClicked(e -> openWallet());
Label copyIcon = new Label();
copyIcon.setLayoutY(3);
copyIcon.getStyleClass().addAll("icon", "highlight");
Tooltip.install(copyIcon, new Tooltip(Res.get("addressTextField.copyToClipboard")));
AwesomeDude.setIcon(copyIcon, AwesomeIcon.COPY);
copyIcon.setOnMouseClicked(e -> GUIUtil.showFeeInfoBeforeExecute(() -> {
copyIcon.setOnMouseClicked(e -> {
if (address.get() != null && address.get().length() > 0)
Utilities.copyToClipboard(address.get());
}));
});
AnchorPane.setRightAnchor(copyIcon, 30.0);
AnchorPane.setRightAnchor(extWalletIcon, 5.0);

View file

@ -21,7 +21,7 @@ import bisq.core.btc.listeners.XmrBalanceListener;
import bisq.core.btc.model.XmrAddressEntry;
import bisq.core.btc.wallet.XmrWalletService;
import bisq.core.locale.Res;
import bisq.core.util.ParsingUtils;
import bisq.core.trade.HavenoUtils;
import bisq.core.util.coin.CoinFormatter;
import bisq.desktop.components.indicator.TxConfidenceIndicator;
import bisq.desktop.util.GUIUtil;
@ -66,15 +66,14 @@ class DepositListItem {
balanceListener = new XmrBalanceListener(addressEntry.getSubaddressIndex()) {
@Override
public void onBalanceChanged(BigInteger balance) {
DepositListItem.this.balanceAsCoin = ParsingUtils.atomicUnitsToCoin(balance);
DepositListItem.this.balanceAsCoin = HavenoUtils.atomicUnitsToCoin(balance);
DepositListItem.this.balance.set(formatter.formatCoin(balanceAsCoin));
updateUsage(addressEntry.getSubaddressIndex());
}
};
xmrWalletService.addBalanceListener(balanceListener);
balanceAsCoin = xmrWalletService.getBalanceForSubaddress(addressEntry.getSubaddressIndex()); // TODO: Coin represents centineros everywhere, but here it's atomic units. reconcile
balanceAsCoin = Coin.valueOf(ParsingUtils.atomicUnitsToCentineros(balanceAsCoin.longValue())); // in centineros
balanceAsCoin = xmrWalletService.getBalanceForSubaddress(addressEntry.getSubaddressIndex());
balance.set(formatter.formatCoin(balanceAsCoin));
updateUsage(addressEntry.getSubaddressIndex());

View file

@ -34,6 +34,7 @@ import bisq.core.btc.listeners.XmrBalanceListener;
import bisq.core.btc.model.XmrAddressEntry;
import bisq.core.btc.wallet.XmrWalletService;
import bisq.core.locale.Res;
import bisq.core.trade.HavenoUtils;
import bisq.core.user.Preferences;
import bisq.core.util.FormattingUtils;
import bisq.core.util.ParsingUtils;
@ -166,10 +167,9 @@ public class DepositView extends ActivatableView<VBox, Void> {
qrCodeImageView = new ImageView();
qrCodeImageView.getStyleClass().add("qr-code");
Tooltip.install(qrCodeImageView, new Tooltip(Res.get("shared.openLargeQRWindow")));
qrCodeImageView.setOnMouseClicked(e -> GUIUtil.showFeeInfoBeforeExecute(
() -> UserThread.runAfter(
qrCodeImageView.setOnMouseClicked(e -> UserThread.runAfter(
() -> new QRCodeWindow(getPaymentUri()).show(),
200, TimeUnit.MILLISECONDS)));
200, TimeUnit.MILLISECONDS));
GridPane.setRowIndex(qrCodeImageView, gridRow);
GridPane.setRowSpan(qrCodeImageView, 4);
GridPane.setColumnIndex(qrCodeImageView, 1);
@ -308,7 +308,7 @@ public class DepositView extends ActivatableView<VBox, Void> {
private String getPaymentUri() {
return xmrWalletService.getWallet().getPaymentUri(new MoneroTxConfig()
.setAddress(addressTextField.getAddress())
.setAmount(ParsingUtils.coinToAtomicUnits(getAmountAsCoin()))
.setAmount(HavenoUtils.coinToAtomicUnits(getAmountAsCoin()))
.setNote(paymentLabelString));
}

View file

@ -21,9 +21,9 @@ import bisq.core.btc.wallet.XmrWalletService;
import bisq.core.locale.Res;
import bisq.core.offer.Offer;
import bisq.core.offer.OpenOffer;
import bisq.core.trade.HavenoUtils;
import bisq.core.trade.Tradable;
import bisq.core.trade.Trade;
import bisq.core.util.ParsingUtils;
import bisq.core.util.coin.CoinFormatter;
import bisq.desktop.components.indicator.TxConfidenceIndicator;
import bisq.desktop.util.DisplayUtils;
@ -94,8 +94,8 @@ class TransactionsListItem {
Optional<Tradable> optionalTradable = Optional.ofNullable(transactionAwareTradable)
.map(TransactionAwareTradable::asTradable);
Coin valueSentToMe = ParsingUtils.atomicUnitsToCoin(tx.getIncomingAmount() == null ? new BigInteger("0") : tx.getIncomingAmount());
Coin valueSentFromMe = ParsingUtils.atomicUnitsToCoin(tx.getOutgoingAmount() == null ? new BigInteger("0") : tx.getOutgoingAmount());
Coin valueSentToMe = HavenoUtils.atomicUnitsToCoin(tx.getIncomingAmount() == null ? new BigInteger("0") : tx.getIncomingAmount());
Coin valueSentFromMe = HavenoUtils.atomicUnitsToCoin(tx.getOutgoingAmount() == null ? new BigInteger("0") : tx.getOutgoingAmount());
if (tx.getTransfers().get(0).isIncoming()) {
addressString = ((MoneroIncomingTransfer) tx.getTransfers().get(0)).getAddress();

View file

@ -72,8 +72,7 @@ class WithdrawalListItem {
}
private void updateBalance() {
balance = walletService.getBalanceForSubaddress(addressEntry.getSubaddressIndex()); // TODO: Coin represents centineros everywhere, but here it's atomic units. reconcile
balance = Coin.valueOf(ParsingUtils.atomicUnitsToCentineros(balance.longValue())); // in centineros
balance = walletService.getBalanceForSubaddress(addressEntry.getSubaddressIndex());
if (balance != null)
balanceLabel.setText(formatter.formatCoin(this.balance));
}

View file

@ -32,6 +32,7 @@ import bisq.core.btc.setup.WalletsSetup;
import bisq.core.btc.wallet.XmrWalletService;
import bisq.core.locale.Res;
import bisq.core.provider.fee.FeeService;
import bisq.core.trade.HavenoUtils;
import bisq.core.trade.Trade;
import bisq.core.trade.TradeManager;
import bisq.core.user.DontShowAgainLookup;
@ -191,12 +192,12 @@ public class WithdrawalView extends ActivatableView<VBox, Void> {
// create tx
MoneroTxWallet tx = xmrWalletService.getWallet().createTx(new MoneroTxConfig()
.setAccountIndex(0)
.setAmount(ParsingUtils.coinToAtomicUnits(receiverAmount)) // TODO: rename to centinerosToAtomicUnits()?
.setAmount(HavenoUtils.coinToAtomicUnits(receiverAmount)) // TODO: rename to centinerosToAtomicUnits()?
.setAddress(withdrawToAddress)
.setNote(withdrawMemoTextField.getText()));
// create confirmation message
Coin fee = ParsingUtils.atomicUnitsToCoin(tx.getFee());
Coin fee = HavenoUtils.atomicUnitsToCoin(tx.getFee());
Coin sendersAmount = receiverAmount.add(fee);
String messageText = Res.get("shared.sendFundsDetailsWithFee",
formatter.formatCoinWithCode(sendersAmount),

View file

@ -260,10 +260,6 @@ public abstract class MutableOfferDataModel extends OfferDataModel {
priceFeedService.setCurrencyCode(tradeCurrencyCode.get());
// Set the default values (in rare cases if the fee request was not done yet we get the hard coded default values)
// But offer creation happens usually after that so we should have already the value from the estimation service.
txFeeFromFeeService = feeService.getTxFee(feeTxVsize);
calculateVolume();
calculateTotalToPay();
updateBalance();
@ -557,8 +553,7 @@ public abstract class MutableOfferDataModel extends OfferDataModel {
// The mining fee for the createOfferFee tx is deducted from the createOfferFee and not visible to the trader
final Coin makerFee = getMakerFee();
if (direction != null && amount.get() != null && makerFee != null) {
Coin feeAndSecDeposit = getTxFee().add(getSecurityDeposit());
feeAndSecDeposit = feeAndSecDeposit.add(makerFee);
Coin feeAndSecDeposit = getSecurityDeposit().add(makerFee);
Coin total = isBuyOffer() ? feeAndSecDeposit : feeAndSecDeposit.add(amount.get());
totalToPayAsCoin.set(total);
updateBalance();
@ -569,10 +564,6 @@ public abstract class MutableOfferDataModel extends OfferDataModel {
return isBuyOffer() ? getBuyerSecurityDepositAsCoin() : getSellerSecurityDepositAsCoin();
}
public Coin getTxFee() {
return txFeeFromFeeService;
}
void swapTradeToSavings() {
xmrWalletService.resetAddressEntriesForOpenOffer(offerId);
}

View file

@ -22,7 +22,6 @@ import bisq.desktop.common.view.ActivatableViewAndModel;
import bisq.desktop.components.AddressTextField;
import bisq.desktop.components.AutoTooltipButton;
import bisq.desktop.components.AutoTooltipLabel;
import bisq.desktop.components.AutoTooltipSlideToggleButton;
import bisq.desktop.components.BalanceTextField;
import bisq.desktop.components.BusyAnimation;
import bisq.desktop.components.FundsTextField;
@ -32,10 +31,6 @@ import bisq.desktop.components.TitledGroupBg;
import bisq.desktop.main.MainView;
import bisq.desktop.main.account.AccountView;
import bisq.desktop.main.account.content.fiataccounts.FiatAccountsView;
import bisq.desktop.main.offer.ClosableView;
import bisq.desktop.main.offer.OfferView;
import bisq.desktop.main.offer.OfferViewUtil;
import bisq.desktop.main.offer.SelectableView;
import bisq.desktop.main.overlays.notifications.Notification;
import bisq.desktop.main.overlays.popups.Popup;
import bisq.desktop.main.overlays.windows.OfferDetailsWindow;
@ -391,8 +386,7 @@ public abstract class MutableOfferView<M extends MutableOfferViewModel<?>> exten
model.getTotalToPayInfo(),
tradeAmountText,
model.getSecurityDepositInfo(),
model.getTradeFee(),
model.getTxFee()
model.getTradeFee()
);
new Popup().headLine(Res.get("createOffer.createOfferFundWalletInfo.headline"))
.instruction(message)
@ -770,7 +764,7 @@ public abstract class MutableOfferView<M extends MutableOfferViewModel<?>> exten
missingCoinListener = (observable, oldValue, newValue) -> {
if (!newValue.toString().equals("")) {
final byte[] imageBytes = QRCode
.from(getBitcoinURI())
.from(getMoneroURI())
.withSize(98, 98) // code has 41 elements 8 px is border with 98 we get double scale and min. border
.to(ImageType.PNG)
.stream()
@ -1080,10 +1074,9 @@ public abstract class MutableOfferView<M extends MutableOfferViewModel<?>> exten
qrCodeImageView.setFitWidth(150);
qrCodeImageView.getStyleClass().add("qr-code");
Tooltip.install(qrCodeImageView, new Tooltip(Res.get("shared.openLargeQRWindow")));
qrCodeImageView.setOnMouseClicked(e -> GUIUtil.showFeeInfoBeforeExecute(
() -> UserThread.runAfter(
() -> new QRCodeWindow(getBitcoinURI()).show(),
200, TimeUnit.MILLISECONDS)));
qrCodeImageView.setOnMouseClicked(e -> UserThread.runAfter(
() -> new QRCodeWindow(getMoneroURI()).show(),
200, TimeUnit.MILLISECONDS));
GridPane.setRowIndex(qrCodeImageView, gridRow);
GridPane.setColumnIndex(qrCodeImageView, 1);
GridPane.setRowSpan(qrCodeImageView, 3);
@ -1111,7 +1104,7 @@ public abstract class MutableOfferView<M extends MutableOfferViewModel<?>> exten
label.setPadding(new Insets(5, 0, 0, 0));
Button fundFromExternalWalletButton = new AutoTooltipButton(Res.get("shared.fundFromExternalWalletButton"));
fundFromExternalWalletButton.setDefaultButton(false);
fundFromExternalWalletButton.setOnAction(e -> GUIUtil.showFeeInfoBeforeExecute(this::openWallet));
fundFromExternalWalletButton.setOnAction(e -> openWallet());
waitingForFundsSpinner = new BusyAnimation(false);
waitingForFundsLabel = new AutoTooltipLabel();
waitingForFundsLabel.setPadding(new Insets(5, 0, 0, 0));
@ -1178,7 +1171,7 @@ public abstract class MutableOfferView<M extends MutableOfferViewModel<?>> exten
private void openWallet() {
try {
Utilities.openURI(URI.create(getBitcoinURI()));
Utilities.openURI(URI.create(getMoneroURI()));
} catch (Exception ex) {
log.warn(ex.getMessage());
new Popup().warning(Res.get("shared.openDefaultWalletFailed")).show();
@ -1186,10 +1179,12 @@ public abstract class MutableOfferView<M extends MutableOfferViewModel<?>> exten
}
@NotNull
private String getBitcoinURI() {
return "TODO"; // TODO (woodser): wallet.createPaymentUri();
// return GUIUtil.getBitcoinURI(addressTextField.getAddress(), model.getDataModel().getMissingCoin().get(),
// model.getPaymentLabel());
private String getMoneroURI() {
return GUIUtil.getMoneroURI(
addressTextField.getAddress(),
model.getDataModel().getMissingCoin().get(),
model.getPaymentLabel(),
model.dataModel.getXmrWalletService().getWallet());
}
private void addAmountPriceFields() {
@ -1274,6 +1269,9 @@ public abstract class MutableOfferView<M extends MutableOfferViewModel<?>> exten
firstRowHBox.getChildren().add(2, fixedPriceBox);
if (!secondRowHBox.getChildren().contains(percentagePriceBox))
secondRowHBox.getChildren().add(2, percentagePriceBox);
model.triggerPrice.set("");
model.onTriggerPriceTextFieldChanged();
} else {
firstRowHBox.getChildren().remove(fixedPriceBox);
secondRowHBox.getChildren().remove(percentagePriceBox);
@ -1394,7 +1392,6 @@ public abstract class MutableOfferView<M extends MutableOfferViewModel<?>> exten
addPayInfoEntry(infoGridPane, i++, Res.getWithCol("shared.yourSecurityDeposit"), model.getSecurityDepositInfo());
addPayInfoEntry(infoGridPane, i++, Res.getWithCol("createOffer.fundsBox.offerFee"), model.getTradeFee());
addPayInfoEntry(infoGridPane, i++, Res.getWithCol("createOffer.fundsBox.networkFee"), model.getTxFee());
Separator separator = new Separator();
separator.setOrientation(Orientation.HORIZONTAL);
separator.getStyleClass().add("offer-separator");

View file

@ -648,6 +648,7 @@ public abstract class MutableOfferViewModel<M extends MutableOfferDataModel> ext
}
void onShowPayFundsScreen(Runnable actionHandler) {
actionHandler.run();
showPayFundsScreenDisplayed.set(true);
updateSpinnerInfo();
}
@ -1015,24 +1016,10 @@ public abstract class MutableOfferViewModel<M extends MutableOfferDataModel> ext
public String getFundsStructure() {
String fundsStructure;
fundsStructure = Res.get("createOffer.fundsBox.fundsStructure",
getSecurityDepositWithCode(), getMakerFeePercentage(), getTxFeePercentage());
getSecurityDepositWithCode(), getMakerFeePercentage());
return fundsStructure;
}
public String getTxFee() {
return OfferViewModelUtil.getTradeFeeWithFiatEquivalentAndPercentage(offerUtil,
dataModel.getTxFee(),
dataModel.getAmount().get(),
btcFormatter,
Coin.ZERO
);
}
public String getTxFeePercentage() {
Coin txFeeAsCoin = dataModel.getTxFee();
return GUIUtil.getPercentage(txFeeAsCoin, dataModel.getAmount().get());
}
public PaymentAccount getPaymentAccount() {
return dataModel.getPaymentAccount();
}

View file

@ -41,6 +41,7 @@ import static bisq.core.util.coin.CoinUtil.minCoin;
* needed in that UI element.
*/
public abstract class OfferDataModel extends ActivatableDataModel {
@Getter
protected final XmrWalletService xmrWalletService;
protected final OfferUtil offerUtil;

View file

@ -454,12 +454,11 @@ class TakeOfferDataModel extends OfferDataModel {
// The mining fee for the takeOfferFee tx is deducted from the createOfferFee and not visible to the trader
final Coin takerFee = getTakerFee();
if (offer != null && amount.get() != null && takerFee != null) {
Coin feeAndSecDeposit = getTotalTxFee().add(securityDeposit).add(takerFee);
Coin feeAndSecDeposit = securityDeposit.add(takerFee);
if (isBuyOffer())
totalToPayAsCoin.set(feeAndSecDeposit.add(amount.get()));
else
totalToPayAsCoin.set(feeAndSecDeposit);
updateBalance();
log.debug("totalToPayAsCoin {}", totalToPayAsCoin.get().toFriendlyString());
}
@ -556,10 +555,6 @@ class TakeOfferDataModel extends OfferDataModel {
return CurrencyUtil.getNameByCode(offer.getCurrencyCode());
}
public Coin getTotalTxFee() {
return txFeeFromFeeService.add(getTxFeeForDepositTx()).add(getTxFeeForPayoutTx());
}
@NotNull
private Coin getFundsNeededForTrade() {
return getSecurityDeposit().add(getTxFeeForDepositTx()).add(getTxFeeForPayoutTx());

View file

@ -23,7 +23,6 @@ import bisq.desktop.common.view.FxmlView;
import bisq.desktop.components.AddressTextField;
import bisq.desktop.components.AutoTooltipButton;
import bisq.desktop.components.AutoTooltipLabel;
import bisq.desktop.components.AutoTooltipSlideToggleButton;
import bisq.desktop.components.BalanceTextField;
import bisq.desktop.components.BusyAnimation;
import bisq.desktop.components.FundsTextField;
@ -88,7 +87,6 @@ import javafx.scene.control.Tooltip;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.ColumnConstraints;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Priority;
@ -455,8 +453,7 @@ public class TakeOfferView extends ActivatableViewAndModel<AnchorPane, TakeOffer
model.getTotalToPayInfo(),
tradeAmountText,
model.getSecurityDepositInfo(),
model.getTradeFee(),
model.getTxFee()
model.getTradeFee()
);
String key = "takeOfferFundWalletInfo";
new Popup().headLine(Res.get("takeOffer.takeOfferFundWalletInfo.headline"))
@ -477,7 +474,7 @@ public class TakeOfferView extends ActivatableViewAndModel<AnchorPane, TakeOffer
balanceTextField.setVisible(true);
totalToPayTextField.setFundsStructure(Res.get("takeOffer.fundsBox.fundsStructure",
model.getSecurityDepositWithCode(), model.getTakerFeePercentage(), model.getTxFeePercentage()));
model.getSecurityDepositWithCode(), model.getTakerFeePercentage()));
totalToPayTextField.setContentForInfoPopOver(createInfoPopover());
if (model.dataModel.getIsBtcWalletFunded().get()) {
@ -491,7 +488,7 @@ public class TakeOfferView extends ActivatableViewAndModel<AnchorPane, TakeOffer
}
final byte[] imageBytes = QRCode
.from(getBitcoinURI())
.from(getMoneroURI())
.withSize(98, 98) // code has 41 elements 8 px is border with 98 we get double scale and min. border
.to(ImageType.PNG)
.stream()
@ -861,10 +858,9 @@ public class TakeOfferView extends ActivatableViewAndModel<AnchorPane, TakeOffer
qrCodeImageView.setFitWidth(150);
qrCodeImageView.getStyleClass().add("qr-code");
Tooltip.install(qrCodeImageView, new Tooltip(Res.get("shared.openLargeQRWindow")));
qrCodeImageView.setOnMouseClicked(e -> GUIUtil.showFeeInfoBeforeExecute(
() -> UserThread.runAfter(
() -> new QRCodeWindow(getBitcoinURI()).show(),
200, TimeUnit.MILLISECONDS)));
qrCodeImageView.setOnMouseClicked(e -> UserThread.runAfter(
() -> new QRCodeWindow(getMoneroURI()).show(),
200, TimeUnit.MILLISECONDS));
GridPane.setRowIndex(qrCodeImageView, gridRow);
GridPane.setColumnIndex(qrCodeImageView, 1);
GridPane.setRowSpan(qrCodeImageView, 3);
@ -890,7 +886,7 @@ public class TakeOfferView extends ActivatableViewAndModel<AnchorPane, TakeOffer
label.setPadding(new Insets(5, 0, 0, 0));
Button fundFromExternalWalletButton = new AutoTooltipButton(Res.get("shared.fundFromExternalWalletButton"));
fundFromExternalWalletButton.setDefaultButton(false);
fundFromExternalWalletButton.setOnAction(e -> GUIUtil.showFeeInfoBeforeExecute(this::openWallet));
fundFromExternalWalletButton.setOnAction(e -> openWallet());
waitingForFundsBusyAnimation = new BusyAnimation(false);
waitingForFundsLabel = new AutoTooltipLabel();
waitingForFundsLabel.setPadding(new Insets(5, 0, 0, 0));
@ -955,7 +951,7 @@ public class TakeOfferView extends ActivatableViewAndModel<AnchorPane, TakeOffer
private void openWallet() {
try {
Utilities.openURI(URI.create(getBitcoinURI()));
Utilities.openURI(URI.create(getMoneroURI()));
} catch (Exception ex) {
log.warn(ex.getMessage());
new Popup().warning(Res.get("shared.openDefaultWalletFailed")).show();
@ -963,11 +959,12 @@ public class TakeOfferView extends ActivatableViewAndModel<AnchorPane, TakeOffer
}
@NotNull
private String getBitcoinURI() {
return "TODO";
// return GUIUtil.getBitcoinURI(model.dataModel.getAddressEntry().getAddressString(),
// model.dataModel.getMissingCoin().get(),
// model.getPaymentLabel());
private String getMoneroURI() {
return GUIUtil.getMoneroURI(
model.dataModel.getAddressEntry().getAddressString(),
model.dataModel.getMissingCoin().get(),
model.getPaymentLabel(),
model.dataModel.getXmrWalletService().getWallet());
}
private void addAmountPriceFields() {
@ -1165,7 +1162,6 @@ public class TakeOfferView extends ActivatableViewAndModel<AnchorPane, TakeOffer
addPayInfoEntry(infoGridPane, i++, Res.getWithCol("shared.yourSecurityDeposit"), model.getSecurityDepositInfo());
addPayInfoEntry(infoGridPane, i++, Res.get("takeOffer.fundsBox.offerFee"), model.getTradeFee());
addPayInfoEntry(infoGridPane, i++, Res.get("takeOffer.fundsBox.networkFee"), model.getTxFee());
Separator separator = new Separator();
separator.setOrientation(Orientation.HORIZONTAL);
separator.getStyleClass().add("offer-separator");

View file

@ -51,7 +51,6 @@ import bisq.network.p2p.network.CloseConnectionReason;
import bisq.network.p2p.network.Connection;
import bisq.network.p2p.network.ConnectionListener;
import bisq.common.UserThread;
import bisq.common.app.DevEnv;
import org.bitcoinj.core.Coin;
@ -706,20 +705,6 @@ class TakeOfferViewModel extends ActivatableWithDataModel<TakeOfferDataModel> im
return totalToPay;
}
public String getTxFee() {
return OfferViewModelUtil.getTradeFeeWithFiatEquivalentAndPercentage(offerUtil,
dataModel.getTotalTxFee(),
dataModel.getAmount().get(),
btcFormatter,
Coin.ZERO
);
}
public String getTxFeePercentage() {
Coin txFeeAsCoin = dataModel.getTotalTxFee();
return GUIUtil.getPercentage(txFeeAsCoin, dataModel.getAmount().get());
}
ObservableList<PaymentAccount> getPossiblePaymentAccounts() {
return dataModel.getPossiblePaymentAccounts();
}

View file

@ -42,11 +42,13 @@ import bisq.core.payment.PaymentAccount;
import bisq.core.payment.PaymentAccountList;
import bisq.core.payment.payload.PaymentMethod;
import bisq.core.provider.fee.FeeService;
import bisq.core.trade.HavenoUtils;
import bisq.core.trade.txproof.AssetTxProofResult;
import bisq.core.user.DontShowAgainLookup;
import bisq.core.user.Preferences;
import bisq.core.user.User;
import bisq.core.util.FormattingUtils;
import bisq.core.util.ParsingUtils;
import bisq.core.util.coin.CoinFormatter;
import bisq.network.p2p.P2PService;
@ -135,6 +137,9 @@ import java.util.function.Consumer;
import lombok.extern.slf4j.Slf4j;
import monero.daemon.model.MoneroTx;
import monero.wallet.MoneroWallet;
import monero.wallet.model.MoneroTxConfig;
import org.jetbrains.annotations.NotNull;
import javax.annotation.Nullable;
@ -190,19 +195,6 @@ public class GUIUtil {
});
}
public static void showFeeInfoBeforeExecute(Runnable runnable) {
String key = "miningFeeInfo";
if (!DevEnv.isDevMode() && DontShowAgainLookup.showAgain(key)) {
new Popup().attention(Res.get("guiUtil.miningFeeInfo", String.valueOf(GUIUtil.feeService.getTxFeePerVbyte().value)))
.onClose(runnable)
.useIUnderstandButton()
.show();
DontShowAgainLookup.dontShowAgain(key, true);
} else {
runnable.run();
}
}
public static void exportAccounts(ArrayList<PaymentAccount> accounts,
String fileName,
Preferences preferences,
@ -715,6 +707,13 @@ public class GUIUtil {
.show();
}
public static String getMoneroURI(String address, Coin amount, String label, MoneroWallet wallet) {
return wallet.getPaymentUri(new MoneroTxConfig()
.setAddress(address)
.setAmount(HavenoUtils.coinToAtomicUnits(amount))
.setNote(label));
}
public static String getBitcoinURI(String address, Coin amount, String label) {
return address != null ?
BitcoinURI.convertToBitcoinURI(Address.fromString(Config.baseCurrencyNetworkParameters(),