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:
parent
4dbbcd6217
commit
dd0a307a84
@ -25,10 +25,10 @@ import bisq.core.offer.OpenOfferManager;
|
|||||||
import bisq.core.support.dispute.Dispute;
|
import bisq.core.support.dispute.Dispute;
|
||||||
import bisq.core.support.dispute.refund.RefundManager;
|
import bisq.core.support.dispute.refund.RefundManager;
|
||||||
import bisq.core.trade.ClosedTradableManager;
|
import bisq.core.trade.ClosedTradableManager;
|
||||||
|
import bisq.core.trade.HavenoUtils;
|
||||||
import bisq.core.trade.Trade;
|
import bisq.core.trade.Trade;
|
||||||
import bisq.core.trade.TradeManager;
|
import bisq.core.trade.TradeManager;
|
||||||
import bisq.core.trade.failed.FailedTradesManager;
|
import bisq.core.trade.failed.FailedTradesManager;
|
||||||
import bisq.core.util.ParsingUtils;
|
|
||||||
import bisq.network.p2p.P2PService;
|
import bisq.network.p2p.P2PService;
|
||||||
import java.math.BigInteger;
|
import java.math.BigInteger;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@ -137,7 +137,7 @@ public class Balances {
|
|||||||
} else {
|
} else {
|
||||||
reservedAmt = trade.getContract().isMyRoleBuyer(tradeManager.getKeyRing().getPubKeyRing()) ? offerPayload.getBuyerSecurityDeposit() : offerPayload.getAmount() + offerPayload.getSellerSecurityDeposit();
|
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);
|
reservedTradeBalance.set(sum);
|
||||||
}
|
}
|
||||||
|
@ -17,16 +17,16 @@ import bisq.core.btc.setup.MoneroWalletRpcManager;
|
|||||||
import bisq.core.btc.setup.WalletsSetup;
|
import bisq.core.btc.setup.WalletsSetup;
|
||||||
import bisq.core.offer.Offer;
|
import bisq.core.offer.Offer;
|
||||||
import bisq.core.trade.MakerTrade;
|
import bisq.core.trade.MakerTrade;
|
||||||
import bisq.core.trade.SellerTrade;
|
|
||||||
import bisq.core.trade.Trade;
|
import bisq.core.trade.Trade;
|
||||||
import bisq.core.trade.TradeManager;
|
import bisq.core.trade.TradeManager;
|
||||||
|
import bisq.core.trade.BuyerTrade;
|
||||||
import bisq.core.trade.HavenoUtils;
|
import bisq.core.trade.HavenoUtils;
|
||||||
import bisq.core.util.ParsingUtils;
|
|
||||||
|
|
||||||
import com.google.common.util.concurrent.Service.State;
|
import com.google.common.util.concurrent.Service.State;
|
||||||
import com.google.inject.name.Named;
|
import com.google.inject.name.Named;
|
||||||
import common.utils.JsonUtils;
|
import common.utils.JsonUtils;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
import java.math.BigDecimal;
|
||||||
import java.math.BigInteger;
|
import java.math.BigInteger;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
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_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_WALLET_NAME = "haveno_XMR";
|
||||||
private static final String MONERO_MULTISIG_WALLET_PREFIX = "xmr_multisig_trade_";
|
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
|
public static final double MINER_FEE_TOLERANCE = 0.25; // miner fee must be within percent of estimated fee
|
||||||
private 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 CoreAccountService accountService;
|
||||||
private final CoreMoneroConnectionsService connectionsService;
|
private final CoreMoneroConnectionsService connectionsService;
|
||||||
@ -265,106 +265,86 @@ public class XmrWalletService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create the reserve tx and freeze its inputs. The deposit amount is returned
|
* Create the reserve tx and freeze its inputs. The full amount is returned
|
||||||
* to the sender's payout address. Additional funds are reserved to allow
|
* to the sender's payout address less the trade fee.
|
||||||
* fluctuations in the mining fee.
|
|
||||||
*
|
*
|
||||||
* @param tradeFee - trade fee
|
* @param returnAddress return address for reserved funds
|
||||||
* @param depositAmount - amount needed for the trade minus the trade fee
|
* @param tradeFee trade fee
|
||||||
* @param returnAddress - return address for deposit amount
|
* @param peerAmount amount to give peer
|
||||||
* @param addPadding - reserve additional padding to cover future mining fee
|
* @param securityDeposit security deposit amount
|
||||||
* @return a transaction to reserve a trade
|
* @return a transaction to reserve a trade
|
||||||
*/
|
*/
|
||||||
public MoneroTxWallet createReserveTx(BigInteger tradeFee, String returnAddress, BigInteger depositAmount, boolean addPadding) {
|
public MoneroTxWallet createReserveTx(BigInteger tradeFee, BigInteger peerAmount, BigInteger securityDeposit, String returnAddress) {
|
||||||
MoneroWallet wallet = getWallet();
|
log.info("Creating reserve tx with fee={}, peerAmount={}, securityDeposit={}", tradeFee, peerAmount, securityDeposit);
|
||||||
synchronized (wallet) {
|
return createTradeTx(tradeFee, peerAmount, securityDeposit, returnAddress);
|
||||||
|
|
||||||
// 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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create the multisig deposit tx and freeze its inputs.
|
* 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
|
* @return MoneroTxWallet the multisig deposit tx
|
||||||
*/
|
*/
|
||||||
public MoneroTxWallet createDepositTx(Trade trade) {
|
public MoneroTxWallet createDepositTx(Trade trade) {
|
||||||
BigInteger tradeFee = ParsingUtils.coinToAtomicUnits(trade instanceof MakerTrade ? trade.getOffer().getMakerFee() : trade.getTakerFee());
|
|
||||||
Offer offer = trade.getProcessModel().getOffer();
|
Offer offer = trade.getProcessModel().getOffer();
|
||||||
BigInteger depositAmount = ParsingUtils.coinToAtomicUnits(trade instanceof SellerTrade ? offer.getAmount().add(offer.getSellerSecurityDeposit()) : offer.getBuyerSecurityDeposit());
|
|
||||||
String multisigAddress = trade.getProcessModel().getMultisigAddress();
|
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();
|
MoneroWallet wallet = getWallet();
|
||||||
synchronized (wallet) {
|
synchronized (wallet) {
|
||||||
|
|
||||||
// create deposit tx
|
// binary search to maximize security deposit, thereby minimizing potential dust
|
||||||
MoneroTxWallet depositTx = wallet.createTx(new MoneroTxConfig()
|
MoneroTxWallet tradeTx = null;
|
||||||
.setAccountIndex(0)
|
double appliedTolerance = 0.0; // percent of tolerance to apply, thereby decreasing security deposit
|
||||||
.addDestination(HavenoUtils.getTradeFeeAddress(), tradeFee)
|
double searchDiff = 1.0; // difference for next binary search
|
||||||
.addDestination(multisigAddress, depositAmount));
|
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(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
|
// freeze inputs
|
||||||
for (MoneroOutput input : depositTx.getInputs()) wallet.freezeOutput(input.getKeyImage().getHex());
|
for (MoneroOutput input : tradeTx.getInputs()) wallet.freezeOutput(input.getKeyImage().getHex());
|
||||||
wallet.save();
|
wallet.save();
|
||||||
return depositTx;
|
return tradeTx;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Verify a reserve or deposit transaction used during trading.
|
* Verify a reserve or deposit transaction.
|
||||||
* Checks double spends, deposit amount and destination, trade fee, and mining fee.
|
* Checks double spends, trade fee, deposit amount and destination, and miner fee.
|
||||||
* The transaction is submitted but not relayed to the pool then flushed.
|
* The transaction is submitted to the pool then flushed without relaying.
|
||||||
*
|
*
|
||||||
* @param depositAddress is the expected destination address for the deposit amount
|
* @param tradeFee trade fee
|
||||||
* @param depositAmount is the expected amount deposited to multisig
|
* @param peerAmount amount to give peer
|
||||||
* @param tradeFee is the expected fee for trading
|
* @param securityDeposit security deposit amount
|
||||||
* @param txHash is the transaction hash
|
* @param address expected destination address for the deposit amount
|
||||||
* @param txHex is the transaction hex
|
* @param txHash transaction hash
|
||||||
* @param txKey is the transaction key
|
* @param txHex transaction hex
|
||||||
* @param keyImages are expected key images of inputs, ignored if null
|
* @param txKey transaction key
|
||||||
* @param addPadding verifies depositAmount has additional padding to cover future mining fee
|
* @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();
|
MoneroDaemonRpc daemon = getDaemon();
|
||||||
MoneroWallet wallet = getWallet();
|
MoneroWallet wallet = getWallet();
|
||||||
try {
|
try {
|
||||||
log.info("Verifying trade tx with deposit amount={}", depositAmount);
|
|
||||||
|
|
||||||
// verify tx not submitted to pool
|
// verify tx not submitted to pool
|
||||||
MoneroTx tx = daemon.getTx(txHash);
|
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));
|
if (!result.isGood()) throw new RuntimeException("Failed to submit tx to daemon: " + JsonUtils.serialize(result));
|
||||||
tx = getTx(txHash);
|
tx = getTx(txHash);
|
||||||
|
|
||||||
// verify reserved key images
|
// verify key images
|
||||||
if (keyImages != null) {
|
if (keyImages != null) {
|
||||||
Set<String> txKeyImages = new HashSet<String>();
|
Set<String> txKeyImages = new HashSet<String>();
|
||||||
for (MoneroOutput input : tx.getInputs()) txKeyImages.add(input.getKeyImage().getHex());
|
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");
|
if (tx.getUnlockHeight() != 0) throw new RuntimeException("Unlock height must be 0");
|
||||||
|
|
||||||
// verify trade fee
|
// verify trade fee
|
||||||
@ -391,22 +371,17 @@ public class XmrWalletService {
|
|||||||
if (!check.isGood()) throw new RuntimeException("Invalid proof of trade fee");
|
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());
|
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());
|
BigInteger feeEstimate = getFeeEstimate(tx.getWeight());
|
||||||
double feeDiff = tx.getFee().subtract(feeEstimate).abs().doubleValue() / feeEstimate.doubleValue();
|
double feeDiff = tx.getFee().subtract(feeEstimate).abs().doubleValue() / feeEstimate.doubleValue(); // TODO: use BigDecimal?
|
||||||
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());
|
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);
|
log.info("Trade tx fee {} is within tolerance, diff%={}", tx.getFee(), feeDiff);
|
||||||
|
|
||||||
// verify deposit amount
|
// 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 (!check.isGood()) throw new RuntimeException("Invalid proof of deposit amount");
|
||||||
if (addPadding) {
|
BigInteger minAmount = new BigDecimal(peerAmount.add(securityDeposit)).multiply(new BigDecimal(1.0 - SECURITY_DEPOSIT_TOLERANCE)).toBigInteger();
|
||||||
BigInteger minPadding = BigInteger.valueOf((long) (tx.getFee().multiply(BigInteger.valueOf(MINER_FEE_PADDING_MULTIPLIER)).doubleValue() * (1.0 - MINER_FEE_TOLERANCE)));
|
if (check.getReceivedAmount().compareTo(minAmount) < 0) throw new RuntimeException("Deposit amount is not enough, needed " + minAmount + " but was " + check.getReceivedAmount());
|
||||||
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());
|
|
||||||
}
|
|
||||||
} finally {
|
} finally {
|
||||||
try {
|
try {
|
||||||
daemon.flushTxPool(txHash); // flush tx from pool
|
daemon.flushTxPool(txHash); // flush tx from pool
|
||||||
@ -915,7 +890,6 @@ public class XmrWalletService {
|
|||||||
return getBalanceForSubaddress(wallet.getAddressIndex(address).getIndex());
|
return getBalanceForSubaddress(wallet.getAddressIndex(address).getIndex());
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Coin represents centineros everywhere, but here it's atomic units. reconcile
|
|
||||||
public Coin getBalanceForSubaddress(int subaddressIndex) {
|
public Coin getBalanceForSubaddress(int subaddressIndex) {
|
||||||
|
|
||||||
// get subaddress balance
|
// get subaddress balance
|
||||||
@ -931,12 +905,11 @@ public class XmrWalletService {
|
|||||||
// }
|
// }
|
||||||
|
|
||||||
System.out.println("Returning balance for subaddress " + subaddressIndex + ": " + balance.longValueExact());
|
System.out.println("Returning balance for subaddress " + subaddressIndex + ": " + balance.longValueExact());
|
||||||
|
return HavenoUtils.atomicUnitsToCoin(balance);
|
||||||
return Coin.valueOf(balance.longValueExact());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public Coin getAvailableConfirmedBalance() {
|
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() {
|
public Coin getSavingWalletBalance() {
|
||||||
|
@ -42,7 +42,6 @@ import bisq.core.trade.statistics.TradeStatisticsManager;
|
|||||||
import bisq.core.user.Preferences;
|
import bisq.core.user.Preferences;
|
||||||
import bisq.core.user.User;
|
import bisq.core.user.User;
|
||||||
import bisq.core.util.JsonUtil;
|
import bisq.core.util.JsonUtil;
|
||||||
import bisq.core.util.ParsingUtils;
|
|
||||||
import bisq.core.util.Validator;
|
import bisq.core.util.Validator;
|
||||||
|
|
||||||
import bisq.network.p2p.AckMessage;
|
import bisq.network.p2p.AckMessage;
|
||||||
@ -626,14 +625,15 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
|
|||||||
ErrorMessageHandler errorMessageHandler) {
|
ErrorMessageHandler errorMessageHandler) {
|
||||||
new Thread(() -> {
|
new Thread(() -> {
|
||||||
List<String> errorMessages = new ArrayList<String>();
|
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;
|
if (scheduledOffer.getState() != OpenOffer.State.SCHEDULED) continue;
|
||||||
CountDownLatch latch = new CountDownLatch(1);
|
CountDownLatch latch = new CountDownLatch(1);
|
||||||
processUnpostedOffer(scheduledOffer, (transaction) -> {
|
processUnpostedOffer(scheduledOffer, (transaction) -> {
|
||||||
latch.countDown();
|
latch.countDown();
|
||||||
}, errorMessage -> {
|
}, errorMessage -> {
|
||||||
latch.countDown();
|
onRemoved(scheduledOffer);
|
||||||
errorMessages.add(errorMessage);
|
errorMessages.add(errorMessage);
|
||||||
|
latch.countDown();
|
||||||
});
|
});
|
||||||
HavenoUtils.awaitLatch(latch);
|
HavenoUtils.awaitLatch(latch);
|
||||||
}
|
}
|
||||||
@ -655,7 +655,7 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
|
|||||||
|
|
||||||
// get offer reserve amount
|
// get offer reserve amount
|
||||||
Coin offerReserveAmountCoin = openOffer.getOffer().getReserveAmount();
|
Coin offerReserveAmountCoin = openOffer.getOffer().getReserveAmount();
|
||||||
BigInteger offerReserveAmount = ParsingUtils.centinerosToAtomicUnits(offerReserveAmountCoin.value);
|
BigInteger offerReserveAmount = HavenoUtils.centinerosToAtomicUnits(offerReserveAmountCoin.value);
|
||||||
|
|
||||||
// handle sufficient available balance
|
// handle sufficient available balance
|
||||||
if (xmrWalletService.getWallet().getUnlockedBalance(0).compareTo(offerReserveAmount) >= 0) {
|
if (xmrWalletService.getWallet().getUnlockedBalance(0).compareTo(offerReserveAmount) >= 0) {
|
||||||
@ -773,6 +773,7 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
|
|||||||
|
|
||||||
// set offer state
|
// set offer state
|
||||||
openOffer.setState(OpenOffer.State.AVAILABLE);
|
openOffer.setState(OpenOffer.State.AVAILABLE);
|
||||||
|
requestPersistence();
|
||||||
|
|
||||||
resultHandler.handleResult(transaction);
|
resultHandler.handleResult(transaction);
|
||||||
if (!stopped) {
|
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)
|
// verify maker's reserve tx (double spend, trade fee, trade amount, mining fee)
|
||||||
Offer offer = new Offer(request.getOfferPayload());
|
Offer offer = new Offer(request.getOfferPayload());
|
||||||
BigInteger tradeFee = ParsingUtils.coinToAtomicUnits(offer.getMakerFee());
|
BigInteger tradeFee = HavenoUtils.coinToAtomicUnits(offer.getMakerFee());
|
||||||
BigInteger depositAmount = ParsingUtils.coinToAtomicUnits(offer.getDirection() == OfferDirection.BUY ? offer.getBuyerSecurityDeposit() : offer.getAmount().add(offer.getSellerSecurityDeposit()));
|
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(
|
xmrWalletService.verifyTradeTx(
|
||||||
request.getPayoutAddress(),
|
|
||||||
depositAmount,
|
|
||||||
tradeFee,
|
tradeFee,
|
||||||
|
peerAmount,
|
||||||
|
securityDeposit,
|
||||||
|
request.getPayoutAddress(),
|
||||||
request.getReserveTxHash(),
|
request.getReserveTxHash(),
|
||||||
request.getReserveTxHex(),
|
request.getReserveTxHex(),
|
||||||
request.getReserveTxKey(),
|
request.getReserveTxKey(),
|
||||||
request.getReserveTxKeyImages(),
|
request.getReserveTxKeyImages());
|
||||||
true);
|
|
||||||
|
|
||||||
// arbitrator signs offer to certify they have valid reserve tx
|
// arbitrator signs offer to certify they have valid reserve tx
|
||||||
String offerPayloadAsJson = JsonUtil.objectToJson(request.getOfferPayload());
|
String offerPayloadAsJson = JsonUtil.objectToJson(request.getOfferPayload());
|
||||||
|
@ -21,13 +21,17 @@ import bisq.common.taskrunner.Task;
|
|||||||
import bisq.common.taskrunner.TaskRunner;
|
import bisq.common.taskrunner.TaskRunner;
|
||||||
import bisq.core.btc.model.XmrAddressEntry;
|
import bisq.core.btc.model.XmrAddressEntry;
|
||||||
import bisq.core.offer.Offer;
|
import bisq.core.offer.Offer;
|
||||||
|
import bisq.core.offer.OfferDirection;
|
||||||
import bisq.core.offer.placeoffer.PlaceOfferModel;
|
import bisq.core.offer.placeoffer.PlaceOfferModel;
|
||||||
import bisq.core.util.ParsingUtils;
|
import bisq.core.trade.HavenoUtils;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
import java.math.BigInteger;
|
import java.math.BigInteger;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.bitcoinj.core.Coin;
|
||||||
|
|
||||||
import monero.daemon.model.MoneroOutput;
|
import monero.daemon.model.MoneroOutput;
|
||||||
import monero.wallet.model.MoneroTxWallet;
|
import monero.wallet.model.MoneroTxWallet;
|
||||||
|
|
||||||
@ -46,19 +50,18 @@ public class MakerReserveOfferFunds extends Task<PlaceOfferModel> {
|
|||||||
try {
|
try {
|
||||||
runInterceptHook();
|
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();
|
String returnAddress = model.getXmrWalletService().getOrCreateAddressEntry(offer.getId(), XmrAddressEntry.Context.TRADE_PAYOUT).getAddressString();
|
||||||
BigInteger makerFee = ParsingUtils.coinToAtomicUnits(offer.getMakerFee());
|
MoneroTxWallet reserveTx = model.getXmrWalletService().createReserveTx(makerFee, peerAmount, securityDeposit, returnAddress);
|
||||||
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);
|
|
||||||
|
|
||||||
// collect reserved key images // TODO (woodser): switch to proof of reserve?
|
// collect reserved key images
|
||||||
List<String> reservedKeyImages = new ArrayList<String>();
|
List<String> reservedKeyImages = new ArrayList<String>();
|
||||||
for (MoneroOutput input : reserveTx.getInputs()) reservedKeyImages.add(input.getKeyImage().getHex());
|
for (MoneroOutput input : reserveTx.getInputs()) reservedKeyImages.add(input.getKeyImage().getHex());
|
||||||
|
|
||||||
// save offer state
|
// save offer state
|
||||||
// TODO (woodser): persist
|
|
||||||
model.setReserveTx(reserveTx);
|
model.setReserveTx(reserveTx);
|
||||||
offer.getOfferPayload().setReserveTxKeyImages(reservedKeyImages);
|
offer.getOfferPayload().setReserveTxKeyImages(reservedKeyImages);
|
||||||
offer.setOfferFeePaymentTxId(reserveTx.getHash()); // TODO (woodser): don't use this field
|
offer.setOfferFeePaymentTxId(reserveTx.getHash()); // TODO (woodser): don't use this field
|
||||||
|
@ -68,7 +68,7 @@ import bisq.core.payment.UpiAccount;
|
|||||||
import bisq.core.payment.VerseAccount;
|
import bisq.core.payment.VerseAccount;
|
||||||
import bisq.core.payment.WeChatPayAccount;
|
import bisq.core.payment.WeChatPayAccount;
|
||||||
import bisq.core.payment.WesternUnionAccount;
|
import bisq.core.payment.WesternUnionAccount;
|
||||||
import bisq.core.util.ParsingUtils;
|
import bisq.core.trade.HavenoUtils;
|
||||||
import bisq.core.locale.CurrencyUtil;
|
import bisq.core.locale.CurrencyUtil;
|
||||||
import bisq.core.locale.Res;
|
import bisq.core.locale.Res;
|
||||||
import bisq.core.locale.TradeCurrency;
|
import bisq.core.locale.TradeCurrency;
|
||||||
@ -490,8 +490,8 @@ public final class PaymentMethod implements PersistablePayload, Comparable<Payme
|
|||||||
// TODO: remove this when trade credits supported
|
// TODO: remove this when trade credits supported
|
||||||
boolean isFiat = CurrencyUtil.isFiatCurrency(currencyCode);
|
boolean isFiat = CurrencyUtil.isFiatCurrency(currencyCode);
|
||||||
boolean isStagenet = Config.baseCurrencyNetwork() == BaseCurrencyNetwork.XMR_STAGENET;
|
boolean isStagenet = Config.baseCurrencyNetwork() == BaseCurrencyNetwork.XMR_STAGENET;
|
||||||
if (isFiat && isStagenet && ParsingUtils.centinerosToXmr(riskBasedTradeLimit) > MAX_FIAT_STAGENET_XMR) {
|
if (isFiat && isStagenet && HavenoUtils.centinerosToXmr(riskBasedTradeLimit) > MAX_FIAT_STAGENET_XMR) {
|
||||||
riskBasedTradeLimit = ParsingUtils.xmrToCentineros(MAX_FIAT_STAGENET_XMR);
|
riskBasedTradeLimit = HavenoUtils.xmrToCentineros(MAX_FIAT_STAGENET_XMR);
|
||||||
}
|
}
|
||||||
return Coin.valueOf(riskBasedTradeLimit);
|
return Coin.valueOf(riskBasedTradeLimit);
|
||||||
}
|
}
|
||||||
|
@ -834,8 +834,8 @@ public abstract class DisputeManager<T extends DisputeList<Dispute>> extends Sup
|
|||||||
(contract.isBuyerMakerAndSellerTaker() ? contract.getMakerPayoutAddressString() : contract.getTakerPayoutAddressString()) :
|
(contract.isBuyerMakerAndSellerTaker() ? contract.getMakerPayoutAddressString() : contract.getTakerPayoutAddressString()) :
|
||||||
(contract.isBuyerMakerAndSellerTaker() ? contract.getTakerPayoutAddressString() : contract.getMakerPayoutAddressString());
|
(contract.isBuyerMakerAndSellerTaker() ? contract.getTakerPayoutAddressString() : contract.getMakerPayoutAddressString());
|
||||||
String loserPayoutAddress = winnerPayoutAddress.equals(contract.getMakerPayoutAddressString()) ? 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 winnerPayoutAmount = HavenoUtils.coinToAtomicUnits(disputeResult.getWinner() == Winner.BUYER ? disputeResult.getBuyerPayoutAmount() : disputeResult.getSellerPayoutAmount());
|
||||||
BigInteger loserPayoutAmount = ParsingUtils.coinToAtomicUnits(disputeResult.getWinner() == Winner.BUYER ? disputeResult.getSellerPayoutAmount() : disputeResult.getBuyerPayoutAmount());
|
BigInteger loserPayoutAmount = HavenoUtils.coinToAtomicUnits(disputeResult.getWinner() == Winner.BUYER ? disputeResult.getSellerPayoutAmount() : disputeResult.getBuyerPayoutAmount());
|
||||||
|
|
||||||
// create transaction to get fee estimate
|
// create transaction to get fee estimate
|
||||||
MoneroTxConfig txConfig = new MoneroTxConfig().setAccountIndex(0).setRelay(false);
|
MoneroTxConfig txConfig = new MoneroTxConfig().setAccountIndex(0).setRelay(false);
|
||||||
|
@ -35,9 +35,9 @@ import bisq.core.support.messages.ChatMessage;
|
|||||||
import bisq.core.support.messages.SupportMessage;
|
import bisq.core.support.messages.SupportMessage;
|
||||||
import bisq.core.trade.ClosedTradableManager;
|
import bisq.core.trade.ClosedTradableManager;
|
||||||
import bisq.core.trade.Contract;
|
import bisq.core.trade.Contract;
|
||||||
|
import bisq.core.trade.HavenoUtils;
|
||||||
import bisq.core.trade.Trade;
|
import bisq.core.trade.Trade;
|
||||||
import bisq.core.trade.TradeManager;
|
import bisq.core.trade.TradeManager;
|
||||||
import bisq.core.util.ParsingUtils;
|
|
||||||
|
|
||||||
import bisq.network.p2p.AckMessageSourceType;
|
import bisq.network.p2p.AckMessageSourceType;
|
||||||
import bisq.network.p2p.NodeAddress;
|
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());
|
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");
|
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
|
// verify winner and loser payout amounts
|
||||||
BigInteger txCost = arbitratorSignedPayoutTx.getFee().add(arbitratorSignedPayoutTx.getChangeAmount()); // fee + lost dust change
|
BigInteger txCost = arbitratorSignedPayoutTx.getFee().add(arbitratorSignedPayoutTx.getChangeAmount()); // fee + lost dust change
|
||||||
BigInteger expectedWinnerAmount = ParsingUtils.coinToAtomicUnits(disputeResult.getWinner() == Winner.BUYER ? disputeResult.getBuyerPayoutAmount() : disputeResult.getSellerPayoutAmount());
|
BigInteger expectedWinnerAmount = HavenoUtils.coinToAtomicUnits(disputeResult.getWinner() == Winner.BUYER ? disputeResult.getBuyerPayoutAmount() : disputeResult.getSellerPayoutAmount());
|
||||||
BigInteger expectedLoserAmount = ParsingUtils.coinToAtomicUnits(disputeResult.getWinner() == Winner.BUYER ? disputeResult.getSellerPayoutAmount() : disputeResult.getBuyerPayoutAmount());
|
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
|
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
|
else expectedLoserAmount = expectedLoserAmount.subtract(txCost); // loser pays tx cost
|
||||||
BigInteger actualWinnerAmount = disputeResult.getWinner() == Winner.BUYER ? buyerPayoutDestination.getAmount() : sellerPayoutDestination.getAmount();
|
BigInteger actualWinnerAmount = disputeResult.getWinner() == Winner.BUYER ? buyerPayoutDestination.getAmount() : sellerPayoutDestination.getAmount();
|
||||||
|
@ -29,6 +29,8 @@ import bisq.core.trade.messages.PaymentSentMessage;
|
|||||||
import bisq.core.util.JsonUtil;
|
import bisq.core.util.JsonUtil;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.math.BigInteger;
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.concurrent.CountDownLatch;
|
import java.util.concurrent.CountDownLatch;
|
||||||
@ -36,6 +38,8 @@ import java.util.concurrent.ExecutorService;
|
|||||||
import java.util.concurrent.Executors;
|
import java.util.concurrent.Executors;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
import org.bitcoinj.core.Coin;
|
||||||
|
|
||||||
import com.google.common.base.CaseFormat;
|
import com.google.common.base.CaseFormat;
|
||||||
import com.google.common.base.Charsets;
|
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 LOOPBACK_HOST = "127.0.0.1"; // local loopback address to host Monero node
|
||||||
public static final String LOCALHOST = "localhost";
|
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.
|
* Get address to collect trade fees.
|
||||||
*
|
*
|
||||||
|
@ -710,7 +710,7 @@ public abstract class Trade implements Tradable, Model {
|
|||||||
Preconditions.checkNotNull(buyerPayoutAddress, "Buyer payout address must not be null");
|
Preconditions.checkNotNull(buyerPayoutAddress, "Buyer payout address must not be null");
|
||||||
BigInteger sellerDepositAmount = multisigWallet.getTx(this.getSeller().getDepositTxHash()).getIncomingAmount();
|
BigInteger sellerDepositAmount = multisigWallet.getTx(this.getSeller().getDepositTxHash()).getIncomingAmount();
|
||||||
BigInteger buyerDepositAmount = multisigWallet.getTx(this.getBuyer().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 buyerPayoutAmount = buyerDepositAmount.add(tradeAmount);
|
||||||
BigInteger sellerPayoutAmount = sellerDepositAmount.subtract(tradeAmount);
|
BigInteger sellerPayoutAmount = sellerDepositAmount.subtract(tradeAmount);
|
||||||
|
|
||||||
@ -763,7 +763,7 @@ public abstract class Trade implements Tradable, Model {
|
|||||||
Contract contract = getContract();
|
Contract contract = getContract();
|
||||||
BigInteger sellerDepositAmount = multisigWallet.getTx(getSeller().getDepositTxHash()).getIncomingAmount(); // TODO (woodser): redundancy of processModel.getPreparedDepositTxId() vs this.getDepositTxId() necessary or avoidable?
|
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 buyerDepositAmount = multisigWallet.getTx(getBuyer().getDepositTxHash()).getIncomingAmount();
|
||||||
BigInteger tradeAmount = ParsingUtils.coinToAtomicUnits(getAmount());
|
BigInteger tradeAmount = HavenoUtils.coinToAtomicUnits(getAmount());
|
||||||
|
|
||||||
// describe payout tx
|
// describe payout tx
|
||||||
MoneroTxSet describedTxSet = multisigWallet.describeTxSet(new MoneroTxSet().setMultisigTxHex(payoutTxHex));
|
MoneroTxSet describedTxSet = multisigWallet.describeTxSet(new MoneroTxSet().setMultisigTxHex(payoutTxHex));
|
||||||
|
@ -24,11 +24,11 @@ import bisq.common.crypto.Sig;
|
|||||||
import bisq.common.taskrunner.TaskRunner;
|
import bisq.common.taskrunner.TaskRunner;
|
||||||
import bisq.core.offer.Offer;
|
import bisq.core.offer.Offer;
|
||||||
import bisq.core.offer.OfferDirection;
|
import bisq.core.offer.OfferDirection;
|
||||||
|
import bisq.core.trade.HavenoUtils;
|
||||||
import bisq.core.trade.Trade;
|
import bisq.core.trade.Trade;
|
||||||
import bisq.core.trade.messages.DepositRequest;
|
import bisq.core.trade.messages.DepositRequest;
|
||||||
import bisq.core.trade.messages.DepositResponse;
|
import bisq.core.trade.messages.DepositResponse;
|
||||||
import bisq.core.trade.protocol.TradingPeer;
|
import bisq.core.trade.protocol.TradingPeer;
|
||||||
import bisq.core.util.ParsingUtils;
|
|
||||||
import bisq.network.p2p.NodeAddress;
|
import bisq.network.p2p.NodeAddress;
|
||||||
import bisq.network.p2p.SendDirectMessageListener;
|
import bisq.network.p2p.SendDirectMessageListener;
|
||||||
import common.utils.JsonUtils;
|
import common.utils.JsonUtils;
|
||||||
@ -37,6 +37,9 @@ import java.math.BigInteger;
|
|||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
|
import org.bitcoinj.core.Coin;
|
||||||
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import monero.daemon.MoneroDaemon;
|
import monero.daemon.MoneroDaemon;
|
||||||
import monero.daemon.model.MoneroSubmitTxResult;
|
import monero.daemon.model.MoneroSubmitTxResult;
|
||||||
@ -70,28 +73,30 @@ public class ArbitratorProcessDepositRequest extends TradeTask {
|
|||||||
// set peer's signature
|
// set peer's signature
|
||||||
peer.setContractSignature(signature);
|
peer.setContractSignature(signature);
|
||||||
|
|
||||||
// collect expected values of deposit tx
|
// collect expected values
|
||||||
Offer offer = trade.getOffer();
|
Offer offer = trade.getOffer();
|
||||||
boolean isFromTaker = request.getSenderNodeAddress().equals(trade.getTaker().getNodeAddress());
|
boolean isFromTaker = request.getSenderNodeAddress().equals(trade.getTaker().getNodeAddress());
|
||||||
boolean isFromBuyer = isFromTaker ? offer.getDirection() == OfferDirection.SELL : offer.getDirection() == OfferDirection.BUY;
|
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();
|
String depositAddress = processModel.getMultisigAddress();
|
||||||
BigInteger tradeFee;
|
BigInteger tradeFee;
|
||||||
TradingPeer trader = trade.getTradingPeer(request.getSenderNodeAddress());
|
TradingPeer trader = trade.getTradingPeer(request.getSenderNodeAddress());
|
||||||
if (trader == processModel.getMaker()) tradeFee = ParsingUtils.coinToAtomicUnits(trade.getOffer().getMakerFee());
|
if (trader == processModel.getMaker()) tradeFee = HavenoUtils.coinToAtomicUnits(trade.getOffer().getMakerFee());
|
||||||
else if (trader == processModel.getTaker()) tradeFee = ParsingUtils.coinToAtomicUnits(trade.getTakerFee());
|
else if (trader == processModel.getTaker()) tradeFee = HavenoUtils.coinToAtomicUnits(trade.getTakerFee());
|
||||||
else throw new RuntimeException("DepositRequest is not from maker or taker");
|
else throw new RuntimeException("DepositRequest is not from maker or taker");
|
||||||
|
|
||||||
// verify deposit tx
|
// verify deposit tx
|
||||||
try {
|
try {
|
||||||
trade.getXmrWalletService().verifyTradeTx(depositAddress,
|
trade.getXmrWalletService().verifyTradeTx(
|
||||||
depositAmount,
|
|
||||||
tradeFee,
|
tradeFee,
|
||||||
|
peerAmount,
|
||||||
|
securityDeposit,
|
||||||
|
depositAddress,
|
||||||
trader.getDepositTxHash(),
|
trader.getDepositTxHash(),
|
||||||
request.getDepositTxHex(),
|
request.getDepositTxHex(),
|
||||||
request.getDepositTxKey(),
|
request.getDepositTxKey(),
|
||||||
null,
|
null);
|
||||||
false);
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
throw new RuntimeException("Error processing deposit tx from " + (isFromTaker ? "taker " : "maker ") + request.getSenderNodeAddress() + ", offerId=" + offer.getId() + ": " + e.getMessage());
|
throw new RuntimeException("Error processing deposit tx from " + (isFromTaker ? "taker " : "maker ") + request.getSenderNodeAddress() + ", offerId=" + offer.getId() + ": " + e.getMessage());
|
||||||
}
|
}
|
||||||
|
@ -20,11 +20,14 @@ package bisq.core.trade.protocol.tasks;
|
|||||||
import bisq.common.taskrunner.TaskRunner;
|
import bisq.common.taskrunner.TaskRunner;
|
||||||
import bisq.core.offer.Offer;
|
import bisq.core.offer.Offer;
|
||||||
import bisq.core.offer.OfferDirection;
|
import bisq.core.offer.OfferDirection;
|
||||||
|
import bisq.core.trade.HavenoUtils;
|
||||||
import bisq.core.trade.Trade;
|
import bisq.core.trade.Trade;
|
||||||
import bisq.core.trade.messages.InitTradeRequest;
|
import bisq.core.trade.messages.InitTradeRequest;
|
||||||
import bisq.core.trade.protocol.TradingPeer;
|
import bisq.core.trade.protocol.TradingPeer;
|
||||||
import bisq.core.util.ParsingUtils;
|
|
||||||
import java.math.BigInteger;
|
import java.math.BigInteger;
|
||||||
|
|
||||||
|
import org.bitcoinj.core.Coin;
|
||||||
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
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
|
// TODO (woodser): if signer online, should never be called by maker
|
||||||
|
|
||||||
// process reserve tx with expected terms
|
// process reserve tx with expected terms
|
||||||
BigInteger tradeFee = ParsingUtils.coinToAtomicUnits(isFromTaker ? trade.getTakerFee() : offer.getMakerFee());
|
BigInteger tradeFee = HavenoUtils.coinToAtomicUnits(isFromTaker ? trade.getTakerFee() : offer.getMakerFee());
|
||||||
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());
|
||||||
try {
|
try {
|
||||||
trade.getXmrWalletService().verifyTradeTx(
|
trade.getXmrWalletService().verifyTradeTx(
|
||||||
request.getPayoutAddress(),
|
|
||||||
depositAmount,
|
|
||||||
tradeFee,
|
tradeFee,
|
||||||
|
peerAmount,
|
||||||
|
securityDeposit,
|
||||||
|
request.getPayoutAddress(),
|
||||||
request.getReserveTxHash(),
|
request.getReserveTxHash(),
|
||||||
request.getReserveTxHex(),
|
request.getReserveTxHex(),
|
||||||
request.getReserveTxKey(),
|
request.getReserveTxKey(),
|
||||||
null,
|
null);
|
||||||
true);
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
throw new RuntimeException("Error processing reserve tx from " + (isFromTaker ? "taker " : "maker ") + request.getSenderNodeAddress() + ", offerId=" + offer.getId() + ": " + e.getMessage());
|
throw new RuntimeException("Error processing reserve tx from " + (isFromTaker ? "taker " : "maker ") + request.getSenderNodeAddress() + ", offerId=" + offer.getId() + ": " + e.getMessage());
|
||||||
}
|
}
|
||||||
|
@ -19,11 +19,15 @@ package bisq.core.trade.protocol.tasks;
|
|||||||
|
|
||||||
import bisq.common.taskrunner.TaskRunner;
|
import bisq.common.taskrunner.TaskRunner;
|
||||||
import bisq.core.btc.model.XmrAddressEntry;
|
import bisq.core.btc.model.XmrAddressEntry;
|
||||||
|
import bisq.core.offer.OfferDirection;
|
||||||
|
import bisq.core.trade.HavenoUtils;
|
||||||
import bisq.core.trade.Trade;
|
import bisq.core.trade.Trade;
|
||||||
import bisq.core.util.ParsingUtils;
|
|
||||||
import java.math.BigInteger;
|
import java.math.BigInteger;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.bitcoinj.core.Coin;
|
||||||
|
|
||||||
import monero.daemon.model.MoneroOutput;
|
import monero.daemon.model.MoneroOutput;
|
||||||
import monero.wallet.model.MoneroTxWallet;
|
import monero.wallet.model.MoneroTxWallet;
|
||||||
|
|
||||||
@ -38,11 +42,12 @@ public class TakerReserveTradeFunds extends TradeTask {
|
|||||||
try {
|
try {
|
||||||
runInterceptHook();
|
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();
|
String returnAddress = model.getXmrWalletService().getOrCreateAddressEntry(trade.getOffer().getId(), XmrAddressEntry.Context.TRADE_PAYOUT).getAddressString();
|
||||||
BigInteger takerFee = ParsingUtils.coinToAtomicUnits(trade.getTakerFee());
|
MoneroTxWallet reserveTx = model.getXmrWalletService().createReserveTx(takerFee, peerAmount, securityDeposit, returnAddress);
|
||||||
BigInteger depositAmount = ParsingUtils.centinerosToAtomicUnits(processModel.getFundsNeededForTradeAsLong());
|
|
||||||
MoneroTxWallet reserveTx = model.getXmrWalletService().createReserveTx(takerFee, returnAddress, depositAmount, true);
|
|
||||||
|
|
||||||
// collect reserved key images
|
// collect reserved key images
|
||||||
List<String> reservedKeyImages = new ArrayList<String>();
|
List<String> reservedKeyImages = new ArrayList<String>();
|
||||||
@ -51,7 +56,7 @@ public class TakerReserveTradeFunds extends TradeTask {
|
|||||||
// save process state
|
// save process state
|
||||||
processModel.setReserveTx(reserveTx);
|
processModel.setReserveTx(reserveTx);
|
||||||
processModel.getTaker().setReserveTxKeyImages(reservedKeyImages);
|
processModel.getTaker().setReserveTxKeyImages(reservedKeyImages);
|
||||||
|
processModel.getTradeManager().requestPersistence();
|
||||||
complete();
|
complete();
|
||||||
} catch (Throwable t) {
|
} catch (Throwable t) {
|
||||||
trade.setErrorMessage("An error occurred.\n" +
|
trade.setErrorMessage("An error occurred.\n" +
|
||||||
|
@ -9,58 +9,12 @@ import org.bitcoinj.core.Coin;
|
|||||||
import org.bitcoinj.utils.MonetaryFormat;
|
import org.bitcoinj.utils.MonetaryFormat;
|
||||||
|
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
import java.math.BigDecimal;
|
|
||||||
import java.math.BigInteger;
|
|
||||||
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
@Slf4j
|
@Slf4j
|
||||||
public class ParsingUtils {
|
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) {
|
public static Coin parseToCoin(String input, CoinFormatter coinFormatter) {
|
||||||
return parseToCoin(input, coinFormatter.getMonetaryFormat());
|
return parseToCoin(input, coinFormatter.getMonetaryFormat());
|
||||||
}
|
}
|
||||||
|
@ -451,7 +451,7 @@ createOffer.fundsBox.offerFee=Trade fee
|
|||||||
createOffer.fundsBox.networkFee=Mining fee
|
createOffer.fundsBox.networkFee=Mining fee
|
||||||
createOffer.fundsBox.placeOfferSpinnerInfo=Offer publishing is in progress ...
|
createOffer.fundsBox.placeOfferSpinnerInfo=Offer publishing is in progress ...
|
||||||
createOffer.fundsBox.paymentLabel=Haveno trade with ID {0}
|
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.headline=Your offer has been published
|
||||||
createOffer.success.info=You can manage your open offers at \"Portfolio/My open offers\".
|
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.
|
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"
|
# suppress inspection "TrailingSpacesInProperty"
|
||||||
createOffer.createOfferFundWalletInfo.tradeAmount=- Trade amount: {0} \n
|
createOffer.createOfferFundWalletInfo.tradeAmount=- Trade amount: {0} \n
|
||||||
createOffer.createOfferFundWalletInfo.msg=You need to deposit {0} to this offer.\n\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\
|
The amount is the sum of:\n\
|
||||||
{1}\
|
{1}\
|
||||||
- Your security deposit: {2}\n\
|
- Your security deposit: {2}\n\
|
||||||
- Trading fee: {3}\n\
|
- Trading fee: {3}
|
||||||
- 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.
|
|
||||||
|
|
||||||
# only first part "An error occurred when placing the offer:" has been used before. We added now the rest (need update in existing translations!)
|
# 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\
|
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.networkFee=Total mining fees
|
||||||
takeOffer.fundsBox.takeOfferSpinnerInfo=Take offer in progress ...
|
takeOffer.fundsBox.takeOfferSpinnerInfo=Take offer in progress ...
|
||||||
takeOffer.fundsBox.paymentLabel=Haveno trade with ID {0}
|
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.headline=You have successfully taken an offer.
|
||||||
takeOffer.success.info=You can see the status of your trade at \"Portfolio/Open trades\".
|
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}
|
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
|
takeOffer.takeOfferFundWalletInfo.headline=Fund your trade
|
||||||
# suppress inspection "TrailingSpacesInProperty"
|
# suppress inspection "TrailingSpacesInProperty"
|
||||||
takeOffer.takeOfferFundWalletInfo.tradeAmount=- Trade amount: {0} \n
|
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.alreadyPaidInFunds=If you have already paid in funds you can withdraw it in the \"Funds/Send funds\" screen.
|
||||||
takeOffer.setAmountPrice=Set amount
|
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?
|
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
|
# 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.savedToPath=Trading accounts saved to path:\n{0}
|
||||||
guiUtil.accountExport.noAccountSetup=You don't have trading accounts set up for exporting.
|
guiUtil.accountExport.noAccountSetup=You don't have trading accounts set up for exporting.
|
||||||
guiUtil.accountExport.selectPath=Select path to {0}
|
guiUtil.accountExport.selectPath=Select path to {0}
|
||||||
|
@ -1710,8 +1710,6 @@ systemTray.tooltip=Bisq: Decentralizovaná směnárna bitcoinů
|
|||||||
# GUI Util
|
# 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.savedToPath=Obchodní účty uložené na:\n{0}
|
||||||
guiUtil.accountExport.noAccountSetup=Nemáte nastaveny obchodní účty pro export.
|
guiUtil.accountExport.noAccountSetup=Nemáte nastaveny obchodní účty pro export.
|
||||||
guiUtil.accountExport.selectPath=Vyberte cestu k {0}
|
guiUtil.accountExport.selectPath=Vyberte cestu k {0}
|
||||||
|
@ -1710,8 +1710,6 @@ systemTray.tooltip=Bisq: Ein dezentrales Bitcoin-Tauschbörsen-Netzwerk
|
|||||||
# GUI Util
|
# 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.savedToPath=Handelskonten in Verzeichnis gespeichert:\n{0}
|
||||||
guiUtil.accountExport.noAccountSetup=Sie haben kein Handelskonto zum Exportieren eingerichtet.
|
guiUtil.accountExport.noAccountSetup=Sie haben kein Handelskonto zum Exportieren eingerichtet.
|
||||||
guiUtil.accountExport.selectPath=Verzeichnis auswählen zum {0}
|
guiUtil.accountExport.selectPath=Verzeichnis auswählen zum {0}
|
||||||
|
@ -1710,8 +1710,6 @@ systemTray.tooltip=Bisq: Una red de intercambio de bitcoin descentralizada
|
|||||||
# GUI Util
|
# 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.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.noAccountSetup=No tiene cuentas de intercambio configuradas para exportar.
|
||||||
guiUtil.accountExport.selectPath=Seleccionar directorio a {0}
|
guiUtil.accountExport.selectPath=Seleccionar directorio a {0}
|
||||||
|
@ -1710,8 +1710,6 @@ systemTray.tooltip=Bisq: A decentralized bitcoin exchange network
|
|||||||
# GUI Util
|
# 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.savedToPath=حساب های معاملاتی در مسیر ذیل ذخیره شد:\n{0}
|
||||||
guiUtil.accountExport.noAccountSetup=شما حساب های معاملاتی برای صادرات ندارید.
|
guiUtil.accountExport.noAccountSetup=شما حساب های معاملاتی برای صادرات ندارید.
|
||||||
guiUtil.accountExport.selectPath=انتخاب مسیر به {0}
|
guiUtil.accountExport.selectPath=انتخاب مسیر به {0}
|
||||||
|
@ -1711,8 +1711,6 @@ systemTray.tooltip=Bisq: Une plateforme d''échange décentralisée sur le rése
|
|||||||
# GUI Util
|
# 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.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.noAccountSetup=Vous n'avez pas de comptes de trading configurés pour exportation.
|
||||||
guiUtil.accountExport.selectPath=Sélectionner l''arborescence vers {0}
|
guiUtil.accountExport.selectPath=Sélectionner l''arborescence vers {0}
|
||||||
|
@ -1710,8 +1710,6 @@ systemTray.tooltip=Bisq: una rete di scambio decentralizzata di bitcoin
|
|||||||
# GUI Util
|
# 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.savedToPath=Account di trading salvati nel percorso:\n{0}
|
||||||
guiUtil.accountExport.noAccountSetup=Non hai account di trading impostati per l'esportazione.
|
guiUtil.accountExport.noAccountSetup=Non hai account di trading impostati per l'esportazione.
|
||||||
guiUtil.accountExport.selectPath=Seleziona il percorso per {0}
|
guiUtil.accountExport.selectPath=Seleziona il percorso per {0}
|
||||||
|
@ -1710,8 +1710,6 @@ systemTray.tooltip=Bisq: 分散的ビットコイン取引ネットワーク
|
|||||||
# GUI Util
|
# GUI Util
|
||||||
####################################################################
|
####################################################################
|
||||||
|
|
||||||
guiUtil.miningFeeInfo=外部ウォレットで使用されるマイニング手数料が少なくとも{0} satoshis/vbyte あることを確認してください。 そうでなければ、このトレードトランザクションは承認されない可能性、トレードは係争に終わる可能性があります。
|
|
||||||
|
|
||||||
guiUtil.accountExport.savedToPath=取引アカウントを下記パスに保存しました:\n{0}
|
guiUtil.accountExport.savedToPath=取引アカウントを下記パスに保存しました:\n{0}
|
||||||
guiUtil.accountExport.noAccountSetup=取引アカウントのエクスポート設定がされていません
|
guiUtil.accountExport.noAccountSetup=取引アカウントのエクスポート設定がされていません
|
||||||
guiUtil.accountExport.selectPath={0}のパスを選択
|
guiUtil.accountExport.selectPath={0}のパスを選択
|
||||||
|
@ -1718,8 +1718,6 @@ systemTray.tooltip=Bisq: a rede de exchange decentralizada de bitcoin
|
|||||||
# GUI Util
|
# 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.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.noAccountSetup=Você não tem contas de negociação para exportar.
|
||||||
guiUtil.accountExport.selectPath=Selecione pasta de {0}
|
guiUtil.accountExport.selectPath=Selecione pasta de {0}
|
||||||
|
@ -1708,8 +1708,6 @@ systemTray.tooltip=Bisq: Uma rede de echange de bitcoin descentralizada
|
|||||||
# GUI Util
|
# 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.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.noAccountSetup=Você não tem contas de negociação prontas para exportar.
|
||||||
guiUtil.accountExport.selectPath=Selecione diretório de {0}
|
guiUtil.accountExport.selectPath=Selecione diretório de {0}
|
||||||
|
@ -1710,8 +1710,6 @@ systemTray.tooltip=Bisq: A decentralized bitcoin exchange network
|
|||||||
# GUI Util
|
# 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.savedToPath=Торговые счета в:\n{0}
|
||||||
guiUtil.accountExport.noAccountSetup=У вас нет торговых счетов для экспортирования.
|
guiUtil.accountExport.noAccountSetup=У вас нет торговых счетов для экспортирования.
|
||||||
guiUtil.accountExport.selectPath=Выбрать путь к {0}
|
guiUtil.accountExport.selectPath=Выбрать путь к {0}
|
||||||
|
@ -1710,8 +1710,6 @@ systemTray.tooltip=Bisq: A decentralized bitcoin exchange network
|
|||||||
# GUI Util
|
# 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.savedToPath=บัญชีการค้าที่บันทึกไว้ในเส้นทาง: \n{0}
|
||||||
guiUtil.accountExport.noAccountSetup=คุณไม่มีบัญชีการซื้อขายที่ตั้งค่าไว้สำหรับการส่งออก
|
guiUtil.accountExport.noAccountSetup=คุณไม่มีบัญชีการซื้อขายที่ตั้งค่าไว้สำหรับการส่งออก
|
||||||
guiUtil.accountExport.selectPath=เลือกเส้นทางไปที่ {0}
|
guiUtil.accountExport.selectPath=เลือกเส้นทางไปที่ {0}
|
||||||
|
@ -1712,8 +1712,6 @@ systemTray.tooltip=Bisq: A decentralized bitcoin exchange network
|
|||||||
# GUI Util
|
# 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.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.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}
|
guiUtil.accountExport.selectPath=Chọn đường dẫn đến {0}
|
||||||
|
@ -1714,8 +1714,6 @@ systemTray.tooltip=Bisq:去中心化比特币交易网络
|
|||||||
# GUI Util
|
# GUI Util
|
||||||
####################################################################
|
####################################################################
|
||||||
|
|
||||||
guiUtil.miningFeeInfo=请确保您的外部钱包使用的矿工手续费费用足够高至少为 {0} 聪/字节,以便矿工接受资金交易。\n否则交易所交易无法确认,交易最终将会出现纠纷。
|
|
||||||
|
|
||||||
guiUtil.accountExport.savedToPath=交易账户保存在路径:\n{0}
|
guiUtil.accountExport.savedToPath=交易账户保存在路径:\n{0}
|
||||||
guiUtil.accountExport.noAccountSetup=您没有交易账户设置导出。
|
guiUtil.accountExport.noAccountSetup=您没有交易账户设置导出。
|
||||||
guiUtil.accountExport.selectPath=选择路径 {0}
|
guiUtil.accountExport.selectPath=选择路径 {0}
|
||||||
|
@ -1710,8 +1710,6 @@ systemTray.tooltip=Bisq:去中心化比特幣交易網絡
|
|||||||
# GUI Util
|
# GUI Util
|
||||||
####################################################################
|
####################################################################
|
||||||
|
|
||||||
guiUtil.miningFeeInfo=請確保您的外部錢包使用的礦工手續費費用足夠高至少為 {0} 聰/字節,以便礦工接受資金交易。\n否則交易所交易無法確認,交易最終將會出現糾紛。
|
|
||||||
|
|
||||||
guiUtil.accountExport.savedToPath=交易賬户保存在路徑:\n{0}
|
guiUtil.accountExport.savedToPath=交易賬户保存在路徑:\n{0}
|
||||||
guiUtil.accountExport.noAccountSetup=您沒有交易賬户設置導出。
|
guiUtil.accountExport.noAccountSetup=您沒有交易賬户設置導出。
|
||||||
guiUtil.accountExport.selectPath=選擇路徑 {0}
|
guiUtil.accountExport.selectPath=選擇路徑 {0}
|
||||||
|
@ -3,7 +3,7 @@ package bisq.daemon.grpc;
|
|||||||
import bisq.core.api.CoreApi;
|
import bisq.core.api.CoreApi;
|
||||||
import bisq.core.support.dispute.Attachment;
|
import bisq.core.support.dispute.Attachment;
|
||||||
import bisq.core.support.dispute.DisputeResult;
|
import bisq.core.support.dispute.DisputeResult;
|
||||||
import bisq.core.util.ParsingUtils;
|
import bisq.core.trade.HavenoUtils;
|
||||||
|
|
||||||
import bisq.common.proto.ProtoUtil;
|
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 winner = ProtoUtil.enumFromProto(DisputeResult.Winner.class, req.getWinner().name());
|
||||||
var reason = ProtoUtil.enumFromProto(DisputeResult.Reason.class, req.getReason().name());
|
var reason = ProtoUtil.enumFromProto(DisputeResult.Reason.class, req.getReason().name());
|
||||||
// scale atomic unit to centineros for consistency TODO switch base to atomic units?
|
// 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);
|
coreApi.resolveDispute(req.getTradeId(), winner, reason, req.getSummaryNotes(), customPayoutAmount);
|
||||||
var reply = ResolveDisputeReply.newBuilder().build();
|
var reply = ResolveDisputeReply.newBuilder().build();
|
||||||
responseObserver.onNext(reply);
|
responseObserver.onNext(reply);
|
||||||
|
@ -21,7 +21,7 @@ import bisq.core.api.CoreApi;
|
|||||||
import bisq.core.api.model.OfferInfo;
|
import bisq.core.api.model.OfferInfo;
|
||||||
import bisq.core.offer.Offer;
|
import bisq.core.offer.Offer;
|
||||||
import bisq.core.offer.OpenOffer;
|
import bisq.core.offer.OpenOffer;
|
||||||
import bisq.core.util.ParsingUtils;
|
import bisq.core.trade.HavenoUtils;
|
||||||
import bisq.proto.grpc.CancelOfferReply;
|
import bisq.proto.grpc.CancelOfferReply;
|
||||||
import bisq.proto.grpc.CancelOfferRequest;
|
import bisq.proto.grpc.CancelOfferRequest;
|
||||||
import bisq.proto.grpc.PostOfferReply;
|
import bisq.proto.grpc.PostOfferReply;
|
||||||
@ -154,8 +154,8 @@ class GrpcOffersService extends OffersImplBase {
|
|||||||
req.getPrice(),
|
req.getPrice(),
|
||||||
req.getUseMarketBasedPrice(),
|
req.getUseMarketBasedPrice(),
|
||||||
req.getMarketPriceMarginPct(),
|
req.getMarketPriceMarginPct(),
|
||||||
ParsingUtils.atomicUnitsToCentineros(req.getAmount()), // scale atomic unit to centineros for consistency TODO switch base to atomic units?
|
HavenoUtils.atomicUnitsToCentineros(req.getAmount()), // scale atomic unit to centineros for consistency TODO switch base to atomic units?
|
||||||
ParsingUtils.atomicUnitsToCentineros(req.getMinAmount()),
|
HavenoUtils.atomicUnitsToCentineros(req.getMinAmount()),
|
||||||
req.getBuyerSecurityDepositPct(),
|
req.getBuyerSecurityDepositPct(),
|
||||||
req.getTriggerPrice(),
|
req.getTriggerPrice(),
|
||||||
req.getPaymentAccountId(),
|
req.getPaymentAccountId(),
|
||||||
|
@ -71,8 +71,7 @@ public class AddressTextField extends AnchorPane {
|
|||||||
|
|
||||||
textField.setOnMousePressed(event -> wasPrimaryButtonDown = event.isPrimaryButtonDown());
|
textField.setOnMousePressed(event -> wasPrimaryButtonDown = event.isPrimaryButtonDown());
|
||||||
textField.setOnMouseReleased(event -> {
|
textField.setOnMouseReleased(event -> {
|
||||||
if (wasPrimaryButtonDown)
|
if (wasPrimaryButtonDown) openWallet();
|
||||||
GUIUtil.showFeeInfoBeforeExecute(AddressTextField.this::openWallet);
|
|
||||||
|
|
||||||
wasPrimaryButtonDown = false;
|
wasPrimaryButtonDown = false;
|
||||||
});
|
});
|
||||||
@ -83,17 +82,17 @@ public class AddressTextField extends AnchorPane {
|
|||||||
extWalletIcon.getStyleClass().addAll("icon", "highlight");
|
extWalletIcon.getStyleClass().addAll("icon", "highlight");
|
||||||
extWalletIcon.setTooltip(new Tooltip(tooltipText));
|
extWalletIcon.setTooltip(new Tooltip(tooltipText));
|
||||||
AwesomeDude.setIcon(extWalletIcon, AwesomeIcon.SIGNIN);
|
AwesomeDude.setIcon(extWalletIcon, AwesomeIcon.SIGNIN);
|
||||||
extWalletIcon.setOnMouseClicked(e -> GUIUtil.showFeeInfoBeforeExecute(this::openWallet));
|
extWalletIcon.setOnMouseClicked(e -> openWallet());
|
||||||
|
|
||||||
Label copyIcon = new Label();
|
Label copyIcon = new Label();
|
||||||
copyIcon.setLayoutY(3);
|
copyIcon.setLayoutY(3);
|
||||||
copyIcon.getStyleClass().addAll("icon", "highlight");
|
copyIcon.getStyleClass().addAll("icon", "highlight");
|
||||||
Tooltip.install(copyIcon, new Tooltip(Res.get("addressTextField.copyToClipboard")));
|
Tooltip.install(copyIcon, new Tooltip(Res.get("addressTextField.copyToClipboard")));
|
||||||
AwesomeDude.setIcon(copyIcon, AwesomeIcon.COPY);
|
AwesomeDude.setIcon(copyIcon, AwesomeIcon.COPY);
|
||||||
copyIcon.setOnMouseClicked(e -> GUIUtil.showFeeInfoBeforeExecute(() -> {
|
copyIcon.setOnMouseClicked(e -> {
|
||||||
if (address.get() != null && address.get().length() > 0)
|
if (address.get() != null && address.get().length() > 0)
|
||||||
Utilities.copyToClipboard(address.get());
|
Utilities.copyToClipboard(address.get());
|
||||||
}));
|
});
|
||||||
|
|
||||||
AnchorPane.setRightAnchor(copyIcon, 30.0);
|
AnchorPane.setRightAnchor(copyIcon, 30.0);
|
||||||
AnchorPane.setRightAnchor(extWalletIcon, 5.0);
|
AnchorPane.setRightAnchor(extWalletIcon, 5.0);
|
||||||
|
@ -21,7 +21,7 @@ import bisq.core.btc.listeners.XmrBalanceListener;
|
|||||||
import bisq.core.btc.model.XmrAddressEntry;
|
import bisq.core.btc.model.XmrAddressEntry;
|
||||||
import bisq.core.btc.wallet.XmrWalletService;
|
import bisq.core.btc.wallet.XmrWalletService;
|
||||||
import bisq.core.locale.Res;
|
import bisq.core.locale.Res;
|
||||||
import bisq.core.util.ParsingUtils;
|
import bisq.core.trade.HavenoUtils;
|
||||||
import bisq.core.util.coin.CoinFormatter;
|
import bisq.core.util.coin.CoinFormatter;
|
||||||
import bisq.desktop.components.indicator.TxConfidenceIndicator;
|
import bisq.desktop.components.indicator.TxConfidenceIndicator;
|
||||||
import bisq.desktop.util.GUIUtil;
|
import bisq.desktop.util.GUIUtil;
|
||||||
@ -66,15 +66,14 @@ class DepositListItem {
|
|||||||
balanceListener = new XmrBalanceListener(addressEntry.getSubaddressIndex()) {
|
balanceListener = new XmrBalanceListener(addressEntry.getSubaddressIndex()) {
|
||||||
@Override
|
@Override
|
||||||
public void onBalanceChanged(BigInteger balance) {
|
public void onBalanceChanged(BigInteger balance) {
|
||||||
DepositListItem.this.balanceAsCoin = ParsingUtils.atomicUnitsToCoin(balance);
|
DepositListItem.this.balanceAsCoin = HavenoUtils.atomicUnitsToCoin(balance);
|
||||||
DepositListItem.this.balance.set(formatter.formatCoin(balanceAsCoin));
|
DepositListItem.this.balance.set(formatter.formatCoin(balanceAsCoin));
|
||||||
updateUsage(addressEntry.getSubaddressIndex());
|
updateUsage(addressEntry.getSubaddressIndex());
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
xmrWalletService.addBalanceListener(balanceListener);
|
xmrWalletService.addBalanceListener(balanceListener);
|
||||||
|
|
||||||
balanceAsCoin = xmrWalletService.getBalanceForSubaddress(addressEntry.getSubaddressIndex()); // TODO: Coin represents centineros everywhere, but here it's atomic units. reconcile
|
balanceAsCoin = xmrWalletService.getBalanceForSubaddress(addressEntry.getSubaddressIndex());
|
||||||
balanceAsCoin = Coin.valueOf(ParsingUtils.atomicUnitsToCentineros(balanceAsCoin.longValue())); // in centineros
|
|
||||||
balance.set(formatter.formatCoin(balanceAsCoin));
|
balance.set(formatter.formatCoin(balanceAsCoin));
|
||||||
|
|
||||||
updateUsage(addressEntry.getSubaddressIndex());
|
updateUsage(addressEntry.getSubaddressIndex());
|
||||||
|
@ -34,6 +34,7 @@ import bisq.core.btc.listeners.XmrBalanceListener;
|
|||||||
import bisq.core.btc.model.XmrAddressEntry;
|
import bisq.core.btc.model.XmrAddressEntry;
|
||||||
import bisq.core.btc.wallet.XmrWalletService;
|
import bisq.core.btc.wallet.XmrWalletService;
|
||||||
import bisq.core.locale.Res;
|
import bisq.core.locale.Res;
|
||||||
|
import bisq.core.trade.HavenoUtils;
|
||||||
import bisq.core.user.Preferences;
|
import bisq.core.user.Preferences;
|
||||||
import bisq.core.util.FormattingUtils;
|
import bisq.core.util.FormattingUtils;
|
||||||
import bisq.core.util.ParsingUtils;
|
import bisq.core.util.ParsingUtils;
|
||||||
@ -166,10 +167,9 @@ public class DepositView extends ActivatableView<VBox, Void> {
|
|||||||
qrCodeImageView = new ImageView();
|
qrCodeImageView = new ImageView();
|
||||||
qrCodeImageView.getStyleClass().add("qr-code");
|
qrCodeImageView.getStyleClass().add("qr-code");
|
||||||
Tooltip.install(qrCodeImageView, new Tooltip(Res.get("shared.openLargeQRWindow")));
|
Tooltip.install(qrCodeImageView, new Tooltip(Res.get("shared.openLargeQRWindow")));
|
||||||
qrCodeImageView.setOnMouseClicked(e -> GUIUtil.showFeeInfoBeforeExecute(
|
qrCodeImageView.setOnMouseClicked(e -> UserThread.runAfter(
|
||||||
() -> UserThread.runAfter(
|
|
||||||
() -> new QRCodeWindow(getPaymentUri()).show(),
|
() -> new QRCodeWindow(getPaymentUri()).show(),
|
||||||
200, TimeUnit.MILLISECONDS)));
|
200, TimeUnit.MILLISECONDS));
|
||||||
GridPane.setRowIndex(qrCodeImageView, gridRow);
|
GridPane.setRowIndex(qrCodeImageView, gridRow);
|
||||||
GridPane.setRowSpan(qrCodeImageView, 4);
|
GridPane.setRowSpan(qrCodeImageView, 4);
|
||||||
GridPane.setColumnIndex(qrCodeImageView, 1);
|
GridPane.setColumnIndex(qrCodeImageView, 1);
|
||||||
@ -308,7 +308,7 @@ public class DepositView extends ActivatableView<VBox, Void> {
|
|||||||
private String getPaymentUri() {
|
private String getPaymentUri() {
|
||||||
return xmrWalletService.getWallet().getPaymentUri(new MoneroTxConfig()
|
return xmrWalletService.getWallet().getPaymentUri(new MoneroTxConfig()
|
||||||
.setAddress(addressTextField.getAddress())
|
.setAddress(addressTextField.getAddress())
|
||||||
.setAmount(ParsingUtils.coinToAtomicUnits(getAmountAsCoin()))
|
.setAmount(HavenoUtils.coinToAtomicUnits(getAmountAsCoin()))
|
||||||
.setNote(paymentLabelString));
|
.setNote(paymentLabelString));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -21,9 +21,9 @@ import bisq.core.btc.wallet.XmrWalletService;
|
|||||||
import bisq.core.locale.Res;
|
import bisq.core.locale.Res;
|
||||||
import bisq.core.offer.Offer;
|
import bisq.core.offer.Offer;
|
||||||
import bisq.core.offer.OpenOffer;
|
import bisq.core.offer.OpenOffer;
|
||||||
|
import bisq.core.trade.HavenoUtils;
|
||||||
import bisq.core.trade.Tradable;
|
import bisq.core.trade.Tradable;
|
||||||
import bisq.core.trade.Trade;
|
import bisq.core.trade.Trade;
|
||||||
import bisq.core.util.ParsingUtils;
|
|
||||||
import bisq.core.util.coin.CoinFormatter;
|
import bisq.core.util.coin.CoinFormatter;
|
||||||
import bisq.desktop.components.indicator.TxConfidenceIndicator;
|
import bisq.desktop.components.indicator.TxConfidenceIndicator;
|
||||||
import bisq.desktop.util.DisplayUtils;
|
import bisq.desktop.util.DisplayUtils;
|
||||||
@ -94,8 +94,8 @@ class TransactionsListItem {
|
|||||||
Optional<Tradable> optionalTradable = Optional.ofNullable(transactionAwareTradable)
|
Optional<Tradable> optionalTradable = Optional.ofNullable(transactionAwareTradable)
|
||||||
.map(TransactionAwareTradable::asTradable);
|
.map(TransactionAwareTradable::asTradable);
|
||||||
|
|
||||||
Coin valueSentToMe = ParsingUtils.atomicUnitsToCoin(tx.getIncomingAmount() == null ? new BigInteger("0") : tx.getIncomingAmount());
|
Coin valueSentToMe = HavenoUtils.atomicUnitsToCoin(tx.getIncomingAmount() == null ? new BigInteger("0") : tx.getIncomingAmount());
|
||||||
Coin valueSentFromMe = ParsingUtils.atomicUnitsToCoin(tx.getOutgoingAmount() == null ? new BigInteger("0") : tx.getOutgoingAmount());
|
Coin valueSentFromMe = HavenoUtils.atomicUnitsToCoin(tx.getOutgoingAmount() == null ? new BigInteger("0") : tx.getOutgoingAmount());
|
||||||
|
|
||||||
if (tx.getTransfers().get(0).isIncoming()) {
|
if (tx.getTransfers().get(0).isIncoming()) {
|
||||||
addressString = ((MoneroIncomingTransfer) tx.getTransfers().get(0)).getAddress();
|
addressString = ((MoneroIncomingTransfer) tx.getTransfers().get(0)).getAddress();
|
||||||
|
@ -72,8 +72,7 @@ class WithdrawalListItem {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void updateBalance() {
|
private void updateBalance() {
|
||||||
balance = walletService.getBalanceForSubaddress(addressEntry.getSubaddressIndex()); // TODO: Coin represents centineros everywhere, but here it's atomic units. reconcile
|
balance = walletService.getBalanceForSubaddress(addressEntry.getSubaddressIndex());
|
||||||
balance = Coin.valueOf(ParsingUtils.atomicUnitsToCentineros(balance.longValue())); // in centineros
|
|
||||||
if (balance != null)
|
if (balance != null)
|
||||||
balanceLabel.setText(formatter.formatCoin(this.balance));
|
balanceLabel.setText(formatter.formatCoin(this.balance));
|
||||||
}
|
}
|
||||||
|
@ -32,6 +32,7 @@ import bisq.core.btc.setup.WalletsSetup;
|
|||||||
import bisq.core.btc.wallet.XmrWalletService;
|
import bisq.core.btc.wallet.XmrWalletService;
|
||||||
import bisq.core.locale.Res;
|
import bisq.core.locale.Res;
|
||||||
import bisq.core.provider.fee.FeeService;
|
import bisq.core.provider.fee.FeeService;
|
||||||
|
import bisq.core.trade.HavenoUtils;
|
||||||
import bisq.core.trade.Trade;
|
import bisq.core.trade.Trade;
|
||||||
import bisq.core.trade.TradeManager;
|
import bisq.core.trade.TradeManager;
|
||||||
import bisq.core.user.DontShowAgainLookup;
|
import bisq.core.user.DontShowAgainLookup;
|
||||||
@ -191,12 +192,12 @@ public class WithdrawalView extends ActivatableView<VBox, Void> {
|
|||||||
// create tx
|
// create tx
|
||||||
MoneroTxWallet tx = xmrWalletService.getWallet().createTx(new MoneroTxConfig()
|
MoneroTxWallet tx = xmrWalletService.getWallet().createTx(new MoneroTxConfig()
|
||||||
.setAccountIndex(0)
|
.setAccountIndex(0)
|
||||||
.setAmount(ParsingUtils.coinToAtomicUnits(receiverAmount)) // TODO: rename to centinerosToAtomicUnits()?
|
.setAmount(HavenoUtils.coinToAtomicUnits(receiverAmount)) // TODO: rename to centinerosToAtomicUnits()?
|
||||||
.setAddress(withdrawToAddress)
|
.setAddress(withdrawToAddress)
|
||||||
.setNote(withdrawMemoTextField.getText()));
|
.setNote(withdrawMemoTextField.getText()));
|
||||||
|
|
||||||
// create confirmation message
|
// create confirmation message
|
||||||
Coin fee = ParsingUtils.atomicUnitsToCoin(tx.getFee());
|
Coin fee = HavenoUtils.atomicUnitsToCoin(tx.getFee());
|
||||||
Coin sendersAmount = receiverAmount.add(fee);
|
Coin sendersAmount = receiverAmount.add(fee);
|
||||||
String messageText = Res.get("shared.sendFundsDetailsWithFee",
|
String messageText = Res.get("shared.sendFundsDetailsWithFee",
|
||||||
formatter.formatCoinWithCode(sendersAmount),
|
formatter.formatCoinWithCode(sendersAmount),
|
||||||
|
@ -260,10 +260,6 @@ public abstract class MutableOfferDataModel extends OfferDataModel {
|
|||||||
|
|
||||||
priceFeedService.setCurrencyCode(tradeCurrencyCode.get());
|
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();
|
calculateVolume();
|
||||||
calculateTotalToPay();
|
calculateTotalToPay();
|
||||||
updateBalance();
|
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
|
// The mining fee for the createOfferFee tx is deducted from the createOfferFee and not visible to the trader
|
||||||
final Coin makerFee = getMakerFee();
|
final Coin makerFee = getMakerFee();
|
||||||
if (direction != null && amount.get() != null && makerFee != null) {
|
if (direction != null && amount.get() != null && makerFee != null) {
|
||||||
Coin feeAndSecDeposit = getTxFee().add(getSecurityDeposit());
|
Coin feeAndSecDeposit = getSecurityDeposit().add(makerFee);
|
||||||
feeAndSecDeposit = feeAndSecDeposit.add(makerFee);
|
|
||||||
Coin total = isBuyOffer() ? feeAndSecDeposit : feeAndSecDeposit.add(amount.get());
|
Coin total = isBuyOffer() ? feeAndSecDeposit : feeAndSecDeposit.add(amount.get());
|
||||||
totalToPayAsCoin.set(total);
|
totalToPayAsCoin.set(total);
|
||||||
updateBalance();
|
updateBalance();
|
||||||
@ -569,10 +564,6 @@ public abstract class MutableOfferDataModel extends OfferDataModel {
|
|||||||
return isBuyOffer() ? getBuyerSecurityDepositAsCoin() : getSellerSecurityDepositAsCoin();
|
return isBuyOffer() ? getBuyerSecurityDepositAsCoin() : getSellerSecurityDepositAsCoin();
|
||||||
}
|
}
|
||||||
|
|
||||||
public Coin getTxFee() {
|
|
||||||
return txFeeFromFeeService;
|
|
||||||
}
|
|
||||||
|
|
||||||
void swapTradeToSavings() {
|
void swapTradeToSavings() {
|
||||||
xmrWalletService.resetAddressEntriesForOpenOffer(offerId);
|
xmrWalletService.resetAddressEntriesForOpenOffer(offerId);
|
||||||
}
|
}
|
||||||
|
@ -22,7 +22,6 @@ import bisq.desktop.common.view.ActivatableViewAndModel;
|
|||||||
import bisq.desktop.components.AddressTextField;
|
import bisq.desktop.components.AddressTextField;
|
||||||
import bisq.desktop.components.AutoTooltipButton;
|
import bisq.desktop.components.AutoTooltipButton;
|
||||||
import bisq.desktop.components.AutoTooltipLabel;
|
import bisq.desktop.components.AutoTooltipLabel;
|
||||||
import bisq.desktop.components.AutoTooltipSlideToggleButton;
|
|
||||||
import bisq.desktop.components.BalanceTextField;
|
import bisq.desktop.components.BalanceTextField;
|
||||||
import bisq.desktop.components.BusyAnimation;
|
import bisq.desktop.components.BusyAnimation;
|
||||||
import bisq.desktop.components.FundsTextField;
|
import bisq.desktop.components.FundsTextField;
|
||||||
@ -32,10 +31,6 @@ import bisq.desktop.components.TitledGroupBg;
|
|||||||
import bisq.desktop.main.MainView;
|
import bisq.desktop.main.MainView;
|
||||||
import bisq.desktop.main.account.AccountView;
|
import bisq.desktop.main.account.AccountView;
|
||||||
import bisq.desktop.main.account.content.fiataccounts.FiatAccountsView;
|
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.notifications.Notification;
|
||||||
import bisq.desktop.main.overlays.popups.Popup;
|
import bisq.desktop.main.overlays.popups.Popup;
|
||||||
import bisq.desktop.main.overlays.windows.OfferDetailsWindow;
|
import bisq.desktop.main.overlays.windows.OfferDetailsWindow;
|
||||||
@ -391,8 +386,7 @@ public abstract class MutableOfferView<M extends MutableOfferViewModel<?>> exten
|
|||||||
model.getTotalToPayInfo(),
|
model.getTotalToPayInfo(),
|
||||||
tradeAmountText,
|
tradeAmountText,
|
||||||
model.getSecurityDepositInfo(),
|
model.getSecurityDepositInfo(),
|
||||||
model.getTradeFee(),
|
model.getTradeFee()
|
||||||
model.getTxFee()
|
|
||||||
);
|
);
|
||||||
new Popup().headLine(Res.get("createOffer.createOfferFundWalletInfo.headline"))
|
new Popup().headLine(Res.get("createOffer.createOfferFundWalletInfo.headline"))
|
||||||
.instruction(message)
|
.instruction(message)
|
||||||
@ -770,7 +764,7 @@ public abstract class MutableOfferView<M extends MutableOfferViewModel<?>> exten
|
|||||||
missingCoinListener = (observable, oldValue, newValue) -> {
|
missingCoinListener = (observable, oldValue, newValue) -> {
|
||||||
if (!newValue.toString().equals("")) {
|
if (!newValue.toString().equals("")) {
|
||||||
final byte[] imageBytes = QRCode
|
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
|
.withSize(98, 98) // code has 41 elements 8 px is border with 98 we get double scale and min. border
|
||||||
.to(ImageType.PNG)
|
.to(ImageType.PNG)
|
||||||
.stream()
|
.stream()
|
||||||
@ -1080,10 +1074,9 @@ public abstract class MutableOfferView<M extends MutableOfferViewModel<?>> exten
|
|||||||
qrCodeImageView.setFitWidth(150);
|
qrCodeImageView.setFitWidth(150);
|
||||||
qrCodeImageView.getStyleClass().add("qr-code");
|
qrCodeImageView.getStyleClass().add("qr-code");
|
||||||
Tooltip.install(qrCodeImageView, new Tooltip(Res.get("shared.openLargeQRWindow")));
|
Tooltip.install(qrCodeImageView, new Tooltip(Res.get("shared.openLargeQRWindow")));
|
||||||
qrCodeImageView.setOnMouseClicked(e -> GUIUtil.showFeeInfoBeforeExecute(
|
qrCodeImageView.setOnMouseClicked(e -> UserThread.runAfter(
|
||||||
() -> UserThread.runAfter(
|
() -> new QRCodeWindow(getMoneroURI()).show(),
|
||||||
() -> new QRCodeWindow(getBitcoinURI()).show(),
|
200, TimeUnit.MILLISECONDS));
|
||||||
200, TimeUnit.MILLISECONDS)));
|
|
||||||
GridPane.setRowIndex(qrCodeImageView, gridRow);
|
GridPane.setRowIndex(qrCodeImageView, gridRow);
|
||||||
GridPane.setColumnIndex(qrCodeImageView, 1);
|
GridPane.setColumnIndex(qrCodeImageView, 1);
|
||||||
GridPane.setRowSpan(qrCodeImageView, 3);
|
GridPane.setRowSpan(qrCodeImageView, 3);
|
||||||
@ -1111,7 +1104,7 @@ public abstract class MutableOfferView<M extends MutableOfferViewModel<?>> exten
|
|||||||
label.setPadding(new Insets(5, 0, 0, 0));
|
label.setPadding(new Insets(5, 0, 0, 0));
|
||||||
Button fundFromExternalWalletButton = new AutoTooltipButton(Res.get("shared.fundFromExternalWalletButton"));
|
Button fundFromExternalWalletButton = new AutoTooltipButton(Res.get("shared.fundFromExternalWalletButton"));
|
||||||
fundFromExternalWalletButton.setDefaultButton(false);
|
fundFromExternalWalletButton.setDefaultButton(false);
|
||||||
fundFromExternalWalletButton.setOnAction(e -> GUIUtil.showFeeInfoBeforeExecute(this::openWallet));
|
fundFromExternalWalletButton.setOnAction(e -> openWallet());
|
||||||
waitingForFundsSpinner = new BusyAnimation(false);
|
waitingForFundsSpinner = new BusyAnimation(false);
|
||||||
waitingForFundsLabel = new AutoTooltipLabel();
|
waitingForFundsLabel = new AutoTooltipLabel();
|
||||||
waitingForFundsLabel.setPadding(new Insets(5, 0, 0, 0));
|
waitingForFundsLabel.setPadding(new Insets(5, 0, 0, 0));
|
||||||
@ -1178,7 +1171,7 @@ public abstract class MutableOfferView<M extends MutableOfferViewModel<?>> exten
|
|||||||
|
|
||||||
private void openWallet() {
|
private void openWallet() {
|
||||||
try {
|
try {
|
||||||
Utilities.openURI(URI.create(getBitcoinURI()));
|
Utilities.openURI(URI.create(getMoneroURI()));
|
||||||
} catch (Exception ex) {
|
} catch (Exception ex) {
|
||||||
log.warn(ex.getMessage());
|
log.warn(ex.getMessage());
|
||||||
new Popup().warning(Res.get("shared.openDefaultWalletFailed")).show();
|
new Popup().warning(Res.get("shared.openDefaultWalletFailed")).show();
|
||||||
@ -1186,10 +1179,12 @@ public abstract class MutableOfferView<M extends MutableOfferViewModel<?>> exten
|
|||||||
}
|
}
|
||||||
|
|
||||||
@NotNull
|
@NotNull
|
||||||
private String getBitcoinURI() {
|
private String getMoneroURI() {
|
||||||
return "TODO"; // TODO (woodser): wallet.createPaymentUri();
|
return GUIUtil.getMoneroURI(
|
||||||
// return GUIUtil.getBitcoinURI(addressTextField.getAddress(), model.getDataModel().getMissingCoin().get(),
|
addressTextField.getAddress(),
|
||||||
// model.getPaymentLabel());
|
model.getDataModel().getMissingCoin().get(),
|
||||||
|
model.getPaymentLabel(),
|
||||||
|
model.dataModel.getXmrWalletService().getWallet());
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addAmountPriceFields() {
|
private void addAmountPriceFields() {
|
||||||
@ -1274,6 +1269,9 @@ public abstract class MutableOfferView<M extends MutableOfferViewModel<?>> exten
|
|||||||
firstRowHBox.getChildren().add(2, fixedPriceBox);
|
firstRowHBox.getChildren().add(2, fixedPriceBox);
|
||||||
if (!secondRowHBox.getChildren().contains(percentagePriceBox))
|
if (!secondRowHBox.getChildren().contains(percentagePriceBox))
|
||||||
secondRowHBox.getChildren().add(2, percentagePriceBox);
|
secondRowHBox.getChildren().add(2, percentagePriceBox);
|
||||||
|
|
||||||
|
model.triggerPrice.set("");
|
||||||
|
model.onTriggerPriceTextFieldChanged();
|
||||||
} else {
|
} else {
|
||||||
firstRowHBox.getChildren().remove(fixedPriceBox);
|
firstRowHBox.getChildren().remove(fixedPriceBox);
|
||||||
secondRowHBox.getChildren().remove(percentagePriceBox);
|
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("shared.yourSecurityDeposit"), model.getSecurityDepositInfo());
|
||||||
addPayInfoEntry(infoGridPane, i++, Res.getWithCol("createOffer.fundsBox.offerFee"), model.getTradeFee());
|
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 separator = new Separator();
|
||||||
separator.setOrientation(Orientation.HORIZONTAL);
|
separator.setOrientation(Orientation.HORIZONTAL);
|
||||||
separator.getStyleClass().add("offer-separator");
|
separator.getStyleClass().add("offer-separator");
|
||||||
|
@ -648,6 +648,7 @@ public abstract class MutableOfferViewModel<M extends MutableOfferDataModel> ext
|
|||||||
}
|
}
|
||||||
|
|
||||||
void onShowPayFundsScreen(Runnable actionHandler) {
|
void onShowPayFundsScreen(Runnable actionHandler) {
|
||||||
|
actionHandler.run();
|
||||||
showPayFundsScreenDisplayed.set(true);
|
showPayFundsScreenDisplayed.set(true);
|
||||||
updateSpinnerInfo();
|
updateSpinnerInfo();
|
||||||
}
|
}
|
||||||
@ -1015,24 +1016,10 @@ public abstract class MutableOfferViewModel<M extends MutableOfferDataModel> ext
|
|||||||
public String getFundsStructure() {
|
public String getFundsStructure() {
|
||||||
String fundsStructure;
|
String fundsStructure;
|
||||||
fundsStructure = Res.get("createOffer.fundsBox.fundsStructure",
|
fundsStructure = Res.get("createOffer.fundsBox.fundsStructure",
|
||||||
getSecurityDepositWithCode(), getMakerFeePercentage(), getTxFeePercentage());
|
getSecurityDepositWithCode(), getMakerFeePercentage());
|
||||||
return fundsStructure;
|
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() {
|
public PaymentAccount getPaymentAccount() {
|
||||||
return dataModel.getPaymentAccount();
|
return dataModel.getPaymentAccount();
|
||||||
}
|
}
|
||||||
|
@ -41,6 +41,7 @@ import static bisq.core.util.coin.CoinUtil.minCoin;
|
|||||||
* needed in that UI element.
|
* needed in that UI element.
|
||||||
*/
|
*/
|
||||||
public abstract class OfferDataModel extends ActivatableDataModel {
|
public abstract class OfferDataModel extends ActivatableDataModel {
|
||||||
|
@Getter
|
||||||
protected final XmrWalletService xmrWalletService;
|
protected final XmrWalletService xmrWalletService;
|
||||||
protected final OfferUtil offerUtil;
|
protected final OfferUtil offerUtil;
|
||||||
|
|
||||||
|
@ -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
|
// The mining fee for the takeOfferFee tx is deducted from the createOfferFee and not visible to the trader
|
||||||
final Coin takerFee = getTakerFee();
|
final Coin takerFee = getTakerFee();
|
||||||
if (offer != null && amount.get() != null && takerFee != null) {
|
if (offer != null && amount.get() != null && takerFee != null) {
|
||||||
Coin feeAndSecDeposit = getTotalTxFee().add(securityDeposit).add(takerFee);
|
Coin feeAndSecDeposit = securityDeposit.add(takerFee);
|
||||||
if (isBuyOffer())
|
if (isBuyOffer())
|
||||||
totalToPayAsCoin.set(feeAndSecDeposit.add(amount.get()));
|
totalToPayAsCoin.set(feeAndSecDeposit.add(amount.get()));
|
||||||
else
|
else
|
||||||
totalToPayAsCoin.set(feeAndSecDeposit);
|
totalToPayAsCoin.set(feeAndSecDeposit);
|
||||||
|
|
||||||
updateBalance();
|
updateBalance();
|
||||||
log.debug("totalToPayAsCoin {}", totalToPayAsCoin.get().toFriendlyString());
|
log.debug("totalToPayAsCoin {}", totalToPayAsCoin.get().toFriendlyString());
|
||||||
}
|
}
|
||||||
@ -556,10 +555,6 @@ class TakeOfferDataModel extends OfferDataModel {
|
|||||||
return CurrencyUtil.getNameByCode(offer.getCurrencyCode());
|
return CurrencyUtil.getNameByCode(offer.getCurrencyCode());
|
||||||
}
|
}
|
||||||
|
|
||||||
public Coin getTotalTxFee() {
|
|
||||||
return txFeeFromFeeService.add(getTxFeeForDepositTx()).add(getTxFeeForPayoutTx());
|
|
||||||
}
|
|
||||||
|
|
||||||
@NotNull
|
@NotNull
|
||||||
private Coin getFundsNeededForTrade() {
|
private Coin getFundsNeededForTrade() {
|
||||||
return getSecurityDeposit().add(getTxFeeForDepositTx()).add(getTxFeeForPayoutTx());
|
return getSecurityDeposit().add(getTxFeeForDepositTx()).add(getTxFeeForPayoutTx());
|
||||||
|
@ -23,7 +23,6 @@ import bisq.desktop.common.view.FxmlView;
|
|||||||
import bisq.desktop.components.AddressTextField;
|
import bisq.desktop.components.AddressTextField;
|
||||||
import bisq.desktop.components.AutoTooltipButton;
|
import bisq.desktop.components.AutoTooltipButton;
|
||||||
import bisq.desktop.components.AutoTooltipLabel;
|
import bisq.desktop.components.AutoTooltipLabel;
|
||||||
import bisq.desktop.components.AutoTooltipSlideToggleButton;
|
|
||||||
import bisq.desktop.components.BalanceTextField;
|
import bisq.desktop.components.BalanceTextField;
|
||||||
import bisq.desktop.components.BusyAnimation;
|
import bisq.desktop.components.BusyAnimation;
|
||||||
import bisq.desktop.components.FundsTextField;
|
import bisq.desktop.components.FundsTextField;
|
||||||
@ -88,7 +87,6 @@ import javafx.scene.control.Tooltip;
|
|||||||
import javafx.scene.image.Image;
|
import javafx.scene.image.Image;
|
||||||
import javafx.scene.image.ImageView;
|
import javafx.scene.image.ImageView;
|
||||||
import javafx.scene.layout.AnchorPane;
|
import javafx.scene.layout.AnchorPane;
|
||||||
import javafx.scene.layout.ColumnConstraints;
|
|
||||||
import javafx.scene.layout.GridPane;
|
import javafx.scene.layout.GridPane;
|
||||||
import javafx.scene.layout.HBox;
|
import javafx.scene.layout.HBox;
|
||||||
import javafx.scene.layout.Priority;
|
import javafx.scene.layout.Priority;
|
||||||
@ -455,8 +453,7 @@ public class TakeOfferView extends ActivatableViewAndModel<AnchorPane, TakeOffer
|
|||||||
model.getTotalToPayInfo(),
|
model.getTotalToPayInfo(),
|
||||||
tradeAmountText,
|
tradeAmountText,
|
||||||
model.getSecurityDepositInfo(),
|
model.getSecurityDepositInfo(),
|
||||||
model.getTradeFee(),
|
model.getTradeFee()
|
||||||
model.getTxFee()
|
|
||||||
);
|
);
|
||||||
String key = "takeOfferFundWalletInfo";
|
String key = "takeOfferFundWalletInfo";
|
||||||
new Popup().headLine(Res.get("takeOffer.takeOfferFundWalletInfo.headline"))
|
new Popup().headLine(Res.get("takeOffer.takeOfferFundWalletInfo.headline"))
|
||||||
@ -477,7 +474,7 @@ public class TakeOfferView extends ActivatableViewAndModel<AnchorPane, TakeOffer
|
|||||||
balanceTextField.setVisible(true);
|
balanceTextField.setVisible(true);
|
||||||
|
|
||||||
totalToPayTextField.setFundsStructure(Res.get("takeOffer.fundsBox.fundsStructure",
|
totalToPayTextField.setFundsStructure(Res.get("takeOffer.fundsBox.fundsStructure",
|
||||||
model.getSecurityDepositWithCode(), model.getTakerFeePercentage(), model.getTxFeePercentage()));
|
model.getSecurityDepositWithCode(), model.getTakerFeePercentage()));
|
||||||
totalToPayTextField.setContentForInfoPopOver(createInfoPopover());
|
totalToPayTextField.setContentForInfoPopOver(createInfoPopover());
|
||||||
|
|
||||||
if (model.dataModel.getIsBtcWalletFunded().get()) {
|
if (model.dataModel.getIsBtcWalletFunded().get()) {
|
||||||
@ -491,7 +488,7 @@ public class TakeOfferView extends ActivatableViewAndModel<AnchorPane, TakeOffer
|
|||||||
}
|
}
|
||||||
|
|
||||||
final byte[] imageBytes = QRCode
|
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
|
.withSize(98, 98) // code has 41 elements 8 px is border with 98 we get double scale and min. border
|
||||||
.to(ImageType.PNG)
|
.to(ImageType.PNG)
|
||||||
.stream()
|
.stream()
|
||||||
@ -861,10 +858,9 @@ public class TakeOfferView extends ActivatableViewAndModel<AnchorPane, TakeOffer
|
|||||||
qrCodeImageView.setFitWidth(150);
|
qrCodeImageView.setFitWidth(150);
|
||||||
qrCodeImageView.getStyleClass().add("qr-code");
|
qrCodeImageView.getStyleClass().add("qr-code");
|
||||||
Tooltip.install(qrCodeImageView, new Tooltip(Res.get("shared.openLargeQRWindow")));
|
Tooltip.install(qrCodeImageView, new Tooltip(Res.get("shared.openLargeQRWindow")));
|
||||||
qrCodeImageView.setOnMouseClicked(e -> GUIUtil.showFeeInfoBeforeExecute(
|
qrCodeImageView.setOnMouseClicked(e -> UserThread.runAfter(
|
||||||
() -> UserThread.runAfter(
|
() -> new QRCodeWindow(getMoneroURI()).show(),
|
||||||
() -> new QRCodeWindow(getBitcoinURI()).show(),
|
200, TimeUnit.MILLISECONDS));
|
||||||
200, TimeUnit.MILLISECONDS)));
|
|
||||||
GridPane.setRowIndex(qrCodeImageView, gridRow);
|
GridPane.setRowIndex(qrCodeImageView, gridRow);
|
||||||
GridPane.setColumnIndex(qrCodeImageView, 1);
|
GridPane.setColumnIndex(qrCodeImageView, 1);
|
||||||
GridPane.setRowSpan(qrCodeImageView, 3);
|
GridPane.setRowSpan(qrCodeImageView, 3);
|
||||||
@ -890,7 +886,7 @@ public class TakeOfferView extends ActivatableViewAndModel<AnchorPane, TakeOffer
|
|||||||
label.setPadding(new Insets(5, 0, 0, 0));
|
label.setPadding(new Insets(5, 0, 0, 0));
|
||||||
Button fundFromExternalWalletButton = new AutoTooltipButton(Res.get("shared.fundFromExternalWalletButton"));
|
Button fundFromExternalWalletButton = new AutoTooltipButton(Res.get("shared.fundFromExternalWalletButton"));
|
||||||
fundFromExternalWalletButton.setDefaultButton(false);
|
fundFromExternalWalletButton.setDefaultButton(false);
|
||||||
fundFromExternalWalletButton.setOnAction(e -> GUIUtil.showFeeInfoBeforeExecute(this::openWallet));
|
fundFromExternalWalletButton.setOnAction(e -> openWallet());
|
||||||
waitingForFundsBusyAnimation = new BusyAnimation(false);
|
waitingForFundsBusyAnimation = new BusyAnimation(false);
|
||||||
waitingForFundsLabel = new AutoTooltipLabel();
|
waitingForFundsLabel = new AutoTooltipLabel();
|
||||||
waitingForFundsLabel.setPadding(new Insets(5, 0, 0, 0));
|
waitingForFundsLabel.setPadding(new Insets(5, 0, 0, 0));
|
||||||
@ -955,7 +951,7 @@ public class TakeOfferView extends ActivatableViewAndModel<AnchorPane, TakeOffer
|
|||||||
|
|
||||||
private void openWallet() {
|
private void openWallet() {
|
||||||
try {
|
try {
|
||||||
Utilities.openURI(URI.create(getBitcoinURI()));
|
Utilities.openURI(URI.create(getMoneroURI()));
|
||||||
} catch (Exception ex) {
|
} catch (Exception ex) {
|
||||||
log.warn(ex.getMessage());
|
log.warn(ex.getMessage());
|
||||||
new Popup().warning(Res.get("shared.openDefaultWalletFailed")).show();
|
new Popup().warning(Res.get("shared.openDefaultWalletFailed")).show();
|
||||||
@ -963,11 +959,12 @@ public class TakeOfferView extends ActivatableViewAndModel<AnchorPane, TakeOffer
|
|||||||
}
|
}
|
||||||
|
|
||||||
@NotNull
|
@NotNull
|
||||||
private String getBitcoinURI() {
|
private String getMoneroURI() {
|
||||||
return "TODO";
|
return GUIUtil.getMoneroURI(
|
||||||
// return GUIUtil.getBitcoinURI(model.dataModel.getAddressEntry().getAddressString(),
|
model.dataModel.getAddressEntry().getAddressString(),
|
||||||
// model.dataModel.getMissingCoin().get(),
|
model.dataModel.getMissingCoin().get(),
|
||||||
// model.getPaymentLabel());
|
model.getPaymentLabel(),
|
||||||
|
model.dataModel.getXmrWalletService().getWallet());
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addAmountPriceFields() {
|
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.getWithCol("shared.yourSecurityDeposit"), model.getSecurityDepositInfo());
|
||||||
addPayInfoEntry(infoGridPane, i++, Res.get("takeOffer.fundsBox.offerFee"), model.getTradeFee());
|
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 separator = new Separator();
|
||||||
separator.setOrientation(Orientation.HORIZONTAL);
|
separator.setOrientation(Orientation.HORIZONTAL);
|
||||||
separator.getStyleClass().add("offer-separator");
|
separator.getStyleClass().add("offer-separator");
|
||||||
|
@ -51,7 +51,6 @@ import bisq.network.p2p.network.CloseConnectionReason;
|
|||||||
import bisq.network.p2p.network.Connection;
|
import bisq.network.p2p.network.Connection;
|
||||||
import bisq.network.p2p.network.ConnectionListener;
|
import bisq.network.p2p.network.ConnectionListener;
|
||||||
import bisq.common.UserThread;
|
import bisq.common.UserThread;
|
||||||
import bisq.common.app.DevEnv;
|
|
||||||
|
|
||||||
import org.bitcoinj.core.Coin;
|
import org.bitcoinj.core.Coin;
|
||||||
|
|
||||||
@ -706,20 +705,6 @@ class TakeOfferViewModel extends ActivatableWithDataModel<TakeOfferDataModel> im
|
|||||||
return totalToPay;
|
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() {
|
ObservableList<PaymentAccount> getPossiblePaymentAccounts() {
|
||||||
return dataModel.getPossiblePaymentAccounts();
|
return dataModel.getPossiblePaymentAccounts();
|
||||||
}
|
}
|
||||||
|
@ -42,11 +42,13 @@ import bisq.core.payment.PaymentAccount;
|
|||||||
import bisq.core.payment.PaymentAccountList;
|
import bisq.core.payment.PaymentAccountList;
|
||||||
import bisq.core.payment.payload.PaymentMethod;
|
import bisq.core.payment.payload.PaymentMethod;
|
||||||
import bisq.core.provider.fee.FeeService;
|
import bisq.core.provider.fee.FeeService;
|
||||||
|
import bisq.core.trade.HavenoUtils;
|
||||||
import bisq.core.trade.txproof.AssetTxProofResult;
|
import bisq.core.trade.txproof.AssetTxProofResult;
|
||||||
import bisq.core.user.DontShowAgainLookup;
|
import bisq.core.user.DontShowAgainLookup;
|
||||||
import bisq.core.user.Preferences;
|
import bisq.core.user.Preferences;
|
||||||
import bisq.core.user.User;
|
import bisq.core.user.User;
|
||||||
import bisq.core.util.FormattingUtils;
|
import bisq.core.util.FormattingUtils;
|
||||||
|
import bisq.core.util.ParsingUtils;
|
||||||
import bisq.core.util.coin.CoinFormatter;
|
import bisq.core.util.coin.CoinFormatter;
|
||||||
|
|
||||||
import bisq.network.p2p.P2PService;
|
import bisq.network.p2p.P2PService;
|
||||||
@ -135,6 +137,9 @@ import java.util.function.Consumer;
|
|||||||
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import monero.daemon.model.MoneroTx;
|
import monero.daemon.model.MoneroTx;
|
||||||
|
import monero.wallet.MoneroWallet;
|
||||||
|
import monero.wallet.model.MoneroTxConfig;
|
||||||
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
import javax.annotation.Nullable;
|
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,
|
public static void exportAccounts(ArrayList<PaymentAccount> accounts,
|
||||||
String fileName,
|
String fileName,
|
||||||
Preferences preferences,
|
Preferences preferences,
|
||||||
@ -715,6 +707,13 @@ public class GUIUtil {
|
|||||||
.show();
|
.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) {
|
public static String getBitcoinURI(String address, Coin amount, String label) {
|
||||||
return address != null ?
|
return address != null ?
|
||||||
BitcoinURI.convertToBitcoinURI(Address.fromString(Config.baseCurrencyNetworkParameters(),
|
BitcoinURI.convertToBitcoinURI(Address.fromString(Config.baseCurrencyNetworkParameters(),
|
||||||
|
Loading…
Reference in New Issue
Block a user