mirror of
https://github.com/haveno-dex/haveno.git
synced 2025-08-14 17:35:27 -04:00
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
44 changed files with 263 additions and 353 deletions
|
@ -25,10 +25,10 @@ import bisq.core.offer.OpenOfferManager;
|
|||
import bisq.core.support.dispute.Dispute;
|
||||
import bisq.core.support.dispute.refund.RefundManager;
|
||||
import bisq.core.trade.ClosedTradableManager;
|
||||
import bisq.core.trade.HavenoUtils;
|
||||
import bisq.core.trade.Trade;
|
||||
import bisq.core.trade.TradeManager;
|
||||
import bisq.core.trade.failed.FailedTradesManager;
|
||||
import bisq.core.util.ParsingUtils;
|
||||
import bisq.network.p2p.P2PService;
|
||||
import java.math.BigInteger;
|
||||
import java.util.List;
|
||||
|
@ -137,7 +137,7 @@ public class Balances {
|
|||
} else {
|
||||
reservedAmt = trade.getContract().isMyRoleBuyer(tradeManager.getKeyRing().getPubKeyRing()) ? offerPayload.getBuyerSecurityDeposit() : offerPayload.getAmount() + offerPayload.getSellerSecurityDeposit();
|
||||
}
|
||||
sum = sum.add(Coin.valueOf(ParsingUtils.centinerosToAtomicUnits(reservedAmt).longValueExact()));
|
||||
sum = sum.add(Coin.valueOf(HavenoUtils.centinerosToAtomicUnits(reservedAmt).longValueExact()));
|
||||
}
|
||||
reservedTradeBalance.set(sum);
|
||||
}
|
||||
|
|
|
@ -17,16 +17,16 @@ import bisq.core.btc.setup.MoneroWalletRpcManager;
|
|||
import bisq.core.btc.setup.WalletsSetup;
|
||||
import bisq.core.offer.Offer;
|
||||
import bisq.core.trade.MakerTrade;
|
||||
import bisq.core.trade.SellerTrade;
|
||||
import bisq.core.trade.Trade;
|
||||
import bisq.core.trade.TradeManager;
|
||||
import bisq.core.trade.BuyerTrade;
|
||||
import bisq.core.trade.HavenoUtils;
|
||||
import bisq.core.util.ParsingUtils;
|
||||
|
||||
import com.google.common.util.concurrent.Service.State;
|
||||
import com.google.inject.name.Named;
|
||||
import common.utils.JsonUtils;
|
||||
import java.io.File;
|
||||
import java.math.BigDecimal;
|
||||
import java.math.BigInteger;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
|
@ -83,8 +83,8 @@ public class XmrWalletService {
|
|||
private static final String MONERO_WALLET_RPC_DEFAULT_PASSWORD = "password"; // only used if account password is null
|
||||
private static final String MONERO_WALLET_NAME = "haveno_XMR";
|
||||
private static final String MONERO_MULTISIG_WALLET_PREFIX = "xmr_multisig_trade_";
|
||||
private static final int MINER_FEE_PADDING_MULTIPLIER = 2; // extra padding for miner fees = estimated fee * multiplier
|
||||
private static final double MINER_FEE_TOLERANCE = 0.25; // miner fee must be within percent of estimated fee
|
||||
public static final double MINER_FEE_TOLERANCE = 0.25; // miner fee must be within percent of estimated fee
|
||||
private static final double SECURITY_DEPOSIT_TOLERANCE = Config.baseCurrencyNetwork() == BaseCurrencyNetwork.XMR_LOCAL ? 0.25 : 0.05; // security deposit absorbs miner fee up to percent
|
||||
|
||||
private final CoreAccountService accountService;
|
||||
private final CoreMoneroConnectionsService connectionsService;
|
||||
|
@ -265,106 +265,86 @@ public class XmrWalletService {
|
|||
}
|
||||
|
||||
/**
|
||||
* Create the reserve tx and freeze its inputs. The deposit amount is returned
|
||||
* to the sender's payout address. Additional funds are reserved to allow
|
||||
* fluctuations in the mining fee.
|
||||
* Create the reserve tx and freeze its inputs. The full amount is returned
|
||||
* to the sender's payout address less the trade fee.
|
||||
*
|
||||
* @param tradeFee - trade fee
|
||||
* @param depositAmount - amount needed for the trade minus the trade fee
|
||||
* @param returnAddress - return address for deposit amount
|
||||
* @param addPadding - reserve additional padding to cover future mining fee
|
||||
* @param returnAddress return address for reserved funds
|
||||
* @param tradeFee trade fee
|
||||
* @param peerAmount amount to give peer
|
||||
* @param securityDeposit security deposit amount
|
||||
* @return a transaction to reserve a trade
|
||||
*/
|
||||
public MoneroTxWallet createReserveTx(BigInteger tradeFee, String returnAddress, BigInteger depositAmount, boolean addPadding) {
|
||||
MoneroWallet wallet = getWallet();
|
||||
synchronized (wallet) {
|
||||
|
||||
// add miner fee padding to deposit amount
|
||||
if (addPadding) {
|
||||
|
||||
// get estimated mining fee with deposit amount
|
||||
MoneroTxWallet feeEstimateTx = wallet.createTx(new MoneroTxConfig()
|
||||
.setAccountIndex(0)
|
||||
.addDestination(HavenoUtils.getTradeFeeAddress(), tradeFee)
|
||||
.addDestination(returnAddress, depositAmount));
|
||||
BigInteger feeEstimate = feeEstimateTx.getFee();
|
||||
|
||||
BigInteger daemonFeeEstimate = getFeeEstimate(feeEstimateTx.getWeight());
|
||||
log.info("createReserveTx() 1st feeEstimateTx with weight {} has fee {} versus daemon fee estimate of {} (diff={})", feeEstimateTx.getWeight(), feeEstimateTx.getFee(), daemonFeeEstimate, (feeEstimateTx.getFee().subtract(daemonFeeEstimate)));
|
||||
|
||||
// get estimated mining fee with deposit amount + previous estimated mining fee for better accuracy
|
||||
feeEstimateTx = wallet.createTx(new MoneroTxConfig()
|
||||
.setAccountIndex(0)
|
||||
.addDestination(HavenoUtils.getTradeFeeAddress(), tradeFee)
|
||||
.addDestination(returnAddress, depositAmount.add(feeEstimate.multiply(BigInteger.valueOf(MINER_FEE_PADDING_MULTIPLIER)))));
|
||||
feeEstimate = feeEstimateTx.getFee();
|
||||
|
||||
log.info("createReserveTx() 2nd feeEstimateTx with weight {} has fee {} versus daemon fee estimate of {} (diff={})", feeEstimateTx.getWeight(), feeEstimateTx.getFee(), daemonFeeEstimate, (feeEstimateTx.getFee().subtract(daemonFeeEstimate)));
|
||||
|
||||
// add padding to deposit amount
|
||||
BigInteger minerFeePadding = feeEstimate.multiply(BigInteger.valueOf(MINER_FEE_PADDING_MULTIPLIER));
|
||||
depositAmount = depositAmount.add(minerFeePadding);
|
||||
}
|
||||
|
||||
// create reserve tx
|
||||
MoneroTxWallet reserveTx = wallet.createTx(new MoneroTxConfig()
|
||||
.setAccountIndex(0)
|
||||
.addDestination(HavenoUtils.getTradeFeeAddress(), tradeFee)
|
||||
.addDestination(returnAddress, depositAmount));
|
||||
log.info("Reserve tx weight={}, fee={}, depositAmount={}", reserveTx.getWeight(), reserveTx.getFee(), depositAmount);
|
||||
|
||||
// freeze inputs
|
||||
for (MoneroOutput input : reserveTx.getInputs()) wallet.freezeOutput(input.getKeyImage().getHex());
|
||||
wallet.save();
|
||||
return reserveTx;
|
||||
}
|
||||
public MoneroTxWallet createReserveTx(BigInteger tradeFee, BigInteger peerAmount, BigInteger securityDeposit, String returnAddress) {
|
||||
log.info("Creating reserve tx with fee={}, peerAmount={}, securityDeposit={}", tradeFee, peerAmount, securityDeposit);
|
||||
return createTradeTx(tradeFee, peerAmount, securityDeposit, returnAddress);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the multisig deposit tx and freeze its inputs.
|
||||
*
|
||||
* @param trade the trade to create a deposit tx from
|
||||
* @return MoneroTxWallet the multisig deposit tx
|
||||
*/
|
||||
public MoneroTxWallet createDepositTx(Trade trade) {
|
||||
BigInteger tradeFee = ParsingUtils.coinToAtomicUnits(trade instanceof MakerTrade ? trade.getOffer().getMakerFee() : trade.getTakerFee());
|
||||
Offer offer = trade.getProcessModel().getOffer();
|
||||
BigInteger depositAmount = ParsingUtils.coinToAtomicUnits(trade instanceof SellerTrade ? offer.getAmount().add(offer.getSellerSecurityDeposit()) : offer.getBuyerSecurityDeposit());
|
||||
String multisigAddress = trade.getProcessModel().getMultisigAddress();
|
||||
BigInteger tradeFee = HavenoUtils.coinToAtomicUnits(trade instanceof MakerTrade ? trade.getOffer().getMakerFee() : trade.getTakerFee());
|
||||
BigInteger peerAmount = HavenoUtils.coinToAtomicUnits(trade instanceof BuyerTrade ? Coin.ZERO : offer.getAmount());
|
||||
BigInteger securityDeposit = HavenoUtils.coinToAtomicUnits(trade instanceof BuyerTrade ? offer.getBuyerSecurityDeposit() : offer.getSellerSecurityDeposit());
|
||||
log.info("Creating deposit tx with fee={}, peerAmount={}, securityDeposit={}", tradeFee, peerAmount, securityDeposit);
|
||||
return createTradeTx(tradeFee, peerAmount, securityDeposit, multisigAddress);
|
||||
}
|
||||
|
||||
private MoneroTxWallet createTradeTx(BigInteger tradeFee, BigInteger peerAmount, BigInteger securityDeposit, String address) {
|
||||
MoneroWallet wallet = getWallet();
|
||||
synchronized (wallet) {
|
||||
|
||||
// create deposit tx
|
||||
MoneroTxWallet depositTx = wallet.createTx(new MoneroTxConfig()
|
||||
.setAccountIndex(0)
|
||||
.addDestination(HavenoUtils.getTradeFeeAddress(), tradeFee)
|
||||
.addDestination(multisigAddress, depositAmount));
|
||||
// binary search to maximize security deposit, thereby minimizing potential dust
|
||||
MoneroTxWallet tradeTx = null;
|
||||
double appliedTolerance = 0.0; // percent of tolerance to apply, thereby decreasing security deposit
|
||||
double searchDiff = 1.0; // difference for next binary search
|
||||
BigInteger maxAmount = peerAmount.add(securityDeposit);
|
||||
for (int i = 0; i < 10; i++) {
|
||||
try {
|
||||
BigInteger amount = new BigDecimal(maxAmount).multiply(new BigDecimal(1.0 - SECURITY_DEPOSIT_TOLERANCE * appliedTolerance)).toBigInteger();
|
||||
tradeTx = wallet.createTx(new MoneroTxConfig()
|
||||
.setAccountIndex(0)
|
||||
.addDestination(HavenoUtils.getTradeFeeAddress(), tradeFee)
|
||||
.addDestination(address, amount));
|
||||
appliedTolerance -= searchDiff; // apply less tolerance to increase security deposit
|
||||
if (appliedTolerance < 0.0) break; // can send full security deposit
|
||||
} catch (MoneroError e) {
|
||||
appliedTolerance += searchDiff; // apply more tolerance to decrease security deposit
|
||||
if (appliedTolerance > 1.0) throw e; // not enough money
|
||||
}
|
||||
searchDiff /= 2;
|
||||
}
|
||||
|
||||
// freeze deposit inputs
|
||||
for (MoneroOutput input : depositTx.getInputs()) wallet.freezeOutput(input.getKeyImage().getHex());
|
||||
// freeze inputs
|
||||
for (MoneroOutput input : tradeTx.getInputs()) wallet.freezeOutput(input.getKeyImage().getHex());
|
||||
wallet.save();
|
||||
return depositTx;
|
||||
return tradeTx;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify a reserve or deposit transaction used during trading.
|
||||
* Checks double spends, deposit amount and destination, trade fee, and mining fee.
|
||||
* The transaction is submitted but not relayed to the pool then flushed.
|
||||
* Verify a reserve or deposit transaction.
|
||||
* Checks double spends, trade fee, deposit amount and destination, and miner fee.
|
||||
* The transaction is submitted to the pool then flushed without relaying.
|
||||
*
|
||||
* @param depositAddress is the expected destination address for the deposit amount
|
||||
* @param depositAmount is the expected amount deposited to multisig
|
||||
* @param tradeFee is the expected fee for trading
|
||||
* @param txHash is the transaction hash
|
||||
* @param txHex is the transaction hex
|
||||
* @param txKey is the transaction key
|
||||
* @param keyImages are expected key images of inputs, ignored if null
|
||||
* @param addPadding verifies depositAmount has additional padding to cover future mining fee
|
||||
* @param tradeFee trade fee
|
||||
* @param peerAmount amount to give peer
|
||||
* @param securityDeposit security deposit amount
|
||||
* @param address expected destination address for the deposit amount
|
||||
* @param txHash transaction hash
|
||||
* @param txHex transaction hex
|
||||
* @param txKey transaction key
|
||||
* @param keyImages expected key images of inputs, ignored if null
|
||||
*/
|
||||
public void verifyTradeTx(String depositAddress, BigInteger depositAmount, BigInteger tradeFee, String txHash, String txHex, String txKey, List<String> keyImages, boolean addPadding) {
|
||||
public void verifyTradeTx(BigInteger tradeFee, BigInteger peerAmount, BigInteger securityDeposit, String address, String txHash, String txHex, String txKey, List<String> keyImages) {
|
||||
MoneroDaemonRpc daemon = getDaemon();
|
||||
MoneroWallet wallet = getWallet();
|
||||
try {
|
||||
log.info("Verifying trade tx with deposit amount={}", depositAmount);
|
||||
|
||||
// verify tx not submitted to pool
|
||||
MoneroTx tx = daemon.getTx(txHash);
|
||||
|
@ -375,14 +355,14 @@ public class XmrWalletService {
|
|||
if (!result.isGood()) throw new RuntimeException("Failed to submit tx to daemon: " + JsonUtils.serialize(result));
|
||||
tx = getTx(txHash);
|
||||
|
||||
// verify reserved key images
|
||||
// verify key images
|
||||
if (keyImages != null) {
|
||||
Set<String> txKeyImages = new HashSet<String>();
|
||||
for (MoneroOutput input : tx.getInputs()) txKeyImages.add(input.getKeyImage().getHex());
|
||||
if (!txKeyImages.equals(new HashSet<String>(keyImages))) throw new Error("Reserve tx's inputs do not match claimed key images");
|
||||
if (!txKeyImages.equals(new HashSet<String>(keyImages))) throw new Error("Tx inputs do not match claimed key images");
|
||||
}
|
||||
|
||||
// verify the unlock height
|
||||
// verify unlock height
|
||||
if (tx.getUnlockHeight() != 0) throw new RuntimeException("Unlock height must be 0");
|
||||
|
||||
// verify trade fee
|
||||
|
@ -391,22 +371,17 @@ public class XmrWalletService {
|
|||
if (!check.isGood()) throw new RuntimeException("Invalid proof of trade fee");
|
||||
if (!check.getReceivedAmount().equals(tradeFee)) throw new RuntimeException("Trade fee is incorrect amount, expected " + tradeFee + " but was " + check.getReceivedAmount());
|
||||
|
||||
// verify mining fee
|
||||
// verify miner fee
|
||||
BigInteger feeEstimate = getFeeEstimate(tx.getWeight());
|
||||
double feeDiff = tx.getFee().subtract(feeEstimate).abs().doubleValue() / feeEstimate.doubleValue();
|
||||
if (feeDiff > MINER_FEE_TOLERANCE) throw new Error("Mining fee is not within " + (MINER_FEE_TOLERANCE * 100) + "% of estimated fee, expected " + feeEstimate + " but was " + tx.getFee());
|
||||
double feeDiff = tx.getFee().subtract(feeEstimate).abs().doubleValue() / feeEstimate.doubleValue(); // TODO: use BigDecimal?
|
||||
if (feeDiff > MINER_FEE_TOLERANCE) throw new Error("Miner fee is not within " + (MINER_FEE_TOLERANCE * 100) + "% of estimated fee, expected " + feeEstimate + " but was " + tx.getFee());
|
||||
log.info("Trade tx fee {} is within tolerance, diff%={}", tx.getFee(), feeDiff);
|
||||
|
||||
// verify deposit amount
|
||||
check = wallet.checkTxKey(txHash, txKey, depositAddress);
|
||||
check = wallet.checkTxKey(txHash, txKey, address);
|
||||
if (!check.isGood()) throw new RuntimeException("Invalid proof of deposit amount");
|
||||
if (addPadding) {
|
||||
BigInteger minPadding = BigInteger.valueOf((long) (tx.getFee().multiply(BigInteger.valueOf(MINER_FEE_PADDING_MULTIPLIER)).doubleValue() * (1.0 - MINER_FEE_TOLERANCE)));
|
||||
BigInteger actualPadding = check.getReceivedAmount().subtract(depositAmount);
|
||||
if (actualPadding.compareTo(minPadding) < 0) throw new RuntimeException("Deposit amount is not enough, needed " + depositAmount.add(minPadding) + " (with padding) but was " + check.getReceivedAmount());
|
||||
} else if (check.getReceivedAmount().compareTo(depositAmount) < 0) {
|
||||
throw new RuntimeException("Deposit amount is not enough, needed " + depositAmount + " but was " + check.getReceivedAmount());
|
||||
}
|
||||
BigInteger minAmount = new BigDecimal(peerAmount.add(securityDeposit)).multiply(new BigDecimal(1.0 - SECURITY_DEPOSIT_TOLERANCE)).toBigInteger();
|
||||
if (check.getReceivedAmount().compareTo(minAmount) < 0) throw new RuntimeException("Deposit amount is not enough, needed " + minAmount + " but was " + check.getReceivedAmount());
|
||||
} finally {
|
||||
try {
|
||||
daemon.flushTxPool(txHash); // flush tx from pool
|
||||
|
@ -915,7 +890,6 @@ public class XmrWalletService {
|
|||
return getBalanceForSubaddress(wallet.getAddressIndex(address).getIndex());
|
||||
}
|
||||
|
||||
// TODO: Coin represents centineros everywhere, but here it's atomic units. reconcile
|
||||
public Coin getBalanceForSubaddress(int subaddressIndex) {
|
||||
|
||||
// get subaddress balance
|
||||
|
@ -931,12 +905,11 @@ public class XmrWalletService {
|
|||
// }
|
||||
|
||||
System.out.println("Returning balance for subaddress " + subaddressIndex + ": " + balance.longValueExact());
|
||||
|
||||
return Coin.valueOf(balance.longValueExact());
|
||||
return HavenoUtils.atomicUnitsToCoin(balance);
|
||||
}
|
||||
|
||||
public Coin getAvailableConfirmedBalance() {
|
||||
return wallet != null ? Coin.valueOf(wallet.getUnlockedBalance(0).longValueExact()) : Coin.ZERO;
|
||||
return wallet != null ? HavenoUtils.atomicUnitsToCoin(wallet.getUnlockedBalance(0)) : Coin.ZERO;
|
||||
}
|
||||
|
||||
public Coin getSavingWalletBalance() {
|
||||
|
|
|
@ -42,7 +42,6 @@ import bisq.core.trade.statistics.TradeStatisticsManager;
|
|||
import bisq.core.user.Preferences;
|
||||
import bisq.core.user.User;
|
||||
import bisq.core.util.JsonUtil;
|
||||
import bisq.core.util.ParsingUtils;
|
||||
import bisq.core.util.Validator;
|
||||
|
||||
import bisq.network.p2p.AckMessage;
|
||||
|
@ -626,14 +625,15 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
|
|||
ErrorMessageHandler errorMessageHandler) {
|
||||
new Thread(() -> {
|
||||
List<String> errorMessages = new ArrayList<String>();
|
||||
for (OpenOffer scheduledOffer : openOffers.getObservableList()) {
|
||||
for (OpenOffer scheduledOffer : new ArrayList<OpenOffer>(openOffers.getObservableList())) {
|
||||
if (scheduledOffer.getState() != OpenOffer.State.SCHEDULED) continue;
|
||||
CountDownLatch latch = new CountDownLatch(1);
|
||||
processUnpostedOffer(scheduledOffer, (transaction) -> {
|
||||
latch.countDown();
|
||||
}, errorMessage -> {
|
||||
latch.countDown();
|
||||
onRemoved(scheduledOffer);
|
||||
errorMessages.add(errorMessage);
|
||||
latch.countDown();
|
||||
});
|
||||
HavenoUtils.awaitLatch(latch);
|
||||
}
|
||||
|
@ -655,7 +655,7 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
|
|||
|
||||
// get offer reserve amount
|
||||
Coin offerReserveAmountCoin = openOffer.getOffer().getReserveAmount();
|
||||
BigInteger offerReserveAmount = ParsingUtils.centinerosToAtomicUnits(offerReserveAmountCoin.value);
|
||||
BigInteger offerReserveAmount = HavenoUtils.centinerosToAtomicUnits(offerReserveAmountCoin.value);
|
||||
|
||||
// handle sufficient available balance
|
||||
if (xmrWalletService.getWallet().getUnlockedBalance(0).compareTo(offerReserveAmount) >= 0) {
|
||||
|
@ -773,6 +773,7 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
|
|||
|
||||
// set offer state
|
||||
openOffer.setState(OpenOffer.State.AVAILABLE);
|
||||
requestPersistence();
|
||||
|
||||
resultHandler.handleResult(transaction);
|
||||
if (!stopped) {
|
||||
|
@ -832,17 +833,18 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
|
|||
|
||||
// verify maker's reserve tx (double spend, trade fee, trade amount, mining fee)
|
||||
Offer offer = new Offer(request.getOfferPayload());
|
||||
BigInteger tradeFee = ParsingUtils.coinToAtomicUnits(offer.getMakerFee());
|
||||
BigInteger depositAmount = ParsingUtils.coinToAtomicUnits(offer.getDirection() == OfferDirection.BUY ? offer.getBuyerSecurityDeposit() : offer.getAmount().add(offer.getSellerSecurityDeposit()));
|
||||
BigInteger tradeFee = HavenoUtils.coinToAtomicUnits(offer.getMakerFee());
|
||||
BigInteger peerAmount = HavenoUtils.coinToAtomicUnits(offer.getDirection() == OfferDirection.BUY ? Coin.ZERO : offer.getAmount());
|
||||
BigInteger securityDeposit = HavenoUtils.coinToAtomicUnits(offer.getDirection() == OfferDirection.BUY ? offer.getBuyerSecurityDeposit() : offer.getSellerSecurityDeposit());
|
||||
xmrWalletService.verifyTradeTx(
|
||||
request.getPayoutAddress(),
|
||||
depositAmount,
|
||||
tradeFee,
|
||||
peerAmount,
|
||||
securityDeposit,
|
||||
request.getPayoutAddress(),
|
||||
request.getReserveTxHash(),
|
||||
request.getReserveTxHex(),
|
||||
request.getReserveTxKey(),
|
||||
request.getReserveTxKeyImages(),
|
||||
true);
|
||||
request.getReserveTxKeyImages());
|
||||
|
||||
// arbitrator signs offer to certify they have valid reserve tx
|
||||
String offerPayloadAsJson = JsonUtil.objectToJson(request.getOfferPayload());
|
||||
|
|
|
@ -21,13 +21,17 @@ import bisq.common.taskrunner.Task;
|
|||
import bisq.common.taskrunner.TaskRunner;
|
||||
import bisq.core.btc.model.XmrAddressEntry;
|
||||
import bisq.core.offer.Offer;
|
||||
import bisq.core.offer.OfferDirection;
|
||||
import bisq.core.offer.placeoffer.PlaceOfferModel;
|
||||
import bisq.core.util.ParsingUtils;
|
||||
import bisq.core.trade.HavenoUtils;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import java.math.BigInteger;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.bitcoinj.core.Coin;
|
||||
|
||||
import monero.daemon.model.MoneroOutput;
|
||||
import monero.wallet.model.MoneroTxWallet;
|
||||
|
||||
|
@ -46,19 +50,18 @@ public class MakerReserveOfferFunds extends Task<PlaceOfferModel> {
|
|||
try {
|
||||
runInterceptHook();
|
||||
|
||||
// create reserve tx with padding
|
||||
// create reserve tx
|
||||
BigInteger makerFee = HavenoUtils.coinToAtomicUnits(offer.getMakerFee());
|
||||
BigInteger peerAmount = HavenoUtils.coinToAtomicUnits(offer.getDirection() == OfferDirection.BUY ? Coin.ZERO : offer.getAmount());
|
||||
BigInteger securityDeposit = HavenoUtils.coinToAtomicUnits(offer.getDirection() == OfferDirection.BUY ? offer.getBuyerSecurityDeposit() : offer.getSellerSecurityDeposit());
|
||||
String returnAddress = model.getXmrWalletService().getOrCreateAddressEntry(offer.getId(), XmrAddressEntry.Context.TRADE_PAYOUT).getAddressString();
|
||||
BigInteger makerFee = ParsingUtils.coinToAtomicUnits(offer.getMakerFee());
|
||||
BigInteger depositAmount = ParsingUtils.coinToAtomicUnits(model.getReservedFundsForOffer());
|
||||
log.info("Maker creating reserve tx with maker fee={} and depositAmount={}", makerFee, depositAmount);
|
||||
MoneroTxWallet reserveTx = model.getXmrWalletService().createReserveTx(makerFee, returnAddress, depositAmount, true);
|
||||
MoneroTxWallet reserveTx = model.getXmrWalletService().createReserveTx(makerFee, peerAmount, securityDeposit, returnAddress);
|
||||
|
||||
// collect reserved key images // TODO (woodser): switch to proof of reserve?
|
||||
// collect reserved key images
|
||||
List<String> reservedKeyImages = new ArrayList<String>();
|
||||
for (MoneroOutput input : reserveTx.getInputs()) reservedKeyImages.add(input.getKeyImage().getHex());
|
||||
|
||||
// save offer state
|
||||
// TODO (woodser): persist
|
||||
model.setReserveTx(reserveTx);
|
||||
offer.getOfferPayload().setReserveTxKeyImages(reservedKeyImages);
|
||||
offer.setOfferFeePaymentTxId(reserveTx.getHash()); // TODO (woodser): don't use this field
|
||||
|
|
|
@ -68,7 +68,7 @@ import bisq.core.payment.UpiAccount;
|
|||
import bisq.core.payment.VerseAccount;
|
||||
import bisq.core.payment.WeChatPayAccount;
|
||||
import bisq.core.payment.WesternUnionAccount;
|
||||
import bisq.core.util.ParsingUtils;
|
||||
import bisq.core.trade.HavenoUtils;
|
||||
import bisq.core.locale.CurrencyUtil;
|
||||
import bisq.core.locale.Res;
|
||||
import bisq.core.locale.TradeCurrency;
|
||||
|
@ -490,8 +490,8 @@ public final class PaymentMethod implements PersistablePayload, Comparable<Payme
|
|||
// TODO: remove this when trade credits supported
|
||||
boolean isFiat = CurrencyUtil.isFiatCurrency(currencyCode);
|
||||
boolean isStagenet = Config.baseCurrencyNetwork() == BaseCurrencyNetwork.XMR_STAGENET;
|
||||
if (isFiat && isStagenet && ParsingUtils.centinerosToXmr(riskBasedTradeLimit) > MAX_FIAT_STAGENET_XMR) {
|
||||
riskBasedTradeLimit = ParsingUtils.xmrToCentineros(MAX_FIAT_STAGENET_XMR);
|
||||
if (isFiat && isStagenet && HavenoUtils.centinerosToXmr(riskBasedTradeLimit) > MAX_FIAT_STAGENET_XMR) {
|
||||
riskBasedTradeLimit = HavenoUtils.xmrToCentineros(MAX_FIAT_STAGENET_XMR);
|
||||
}
|
||||
return Coin.valueOf(riskBasedTradeLimit);
|
||||
}
|
||||
|
|
|
@ -834,8 +834,8 @@ public abstract class DisputeManager<T extends DisputeList<Dispute>> extends Sup
|
|||
(contract.isBuyerMakerAndSellerTaker() ? contract.getMakerPayoutAddressString() : contract.getTakerPayoutAddressString()) :
|
||||
(contract.isBuyerMakerAndSellerTaker() ? contract.getTakerPayoutAddressString() : contract.getMakerPayoutAddressString());
|
||||
String loserPayoutAddress = winnerPayoutAddress.equals(contract.getMakerPayoutAddressString()) ? contract.getTakerPayoutAddressString() : contract.getMakerPayoutAddressString();
|
||||
BigInteger winnerPayoutAmount = ParsingUtils.coinToAtomicUnits(disputeResult.getWinner() == Winner.BUYER ? disputeResult.getBuyerPayoutAmount() : disputeResult.getSellerPayoutAmount());
|
||||
BigInteger loserPayoutAmount = ParsingUtils.coinToAtomicUnits(disputeResult.getWinner() == Winner.BUYER ? disputeResult.getSellerPayoutAmount() : disputeResult.getBuyerPayoutAmount());
|
||||
BigInteger winnerPayoutAmount = HavenoUtils.coinToAtomicUnits(disputeResult.getWinner() == Winner.BUYER ? disputeResult.getBuyerPayoutAmount() : disputeResult.getSellerPayoutAmount());
|
||||
BigInteger loserPayoutAmount = HavenoUtils.coinToAtomicUnits(disputeResult.getWinner() == Winner.BUYER ? disputeResult.getSellerPayoutAmount() : disputeResult.getBuyerPayoutAmount());
|
||||
|
||||
// create transaction to get fee estimate
|
||||
MoneroTxConfig txConfig = new MoneroTxConfig().setAccountIndex(0).setRelay(false);
|
||||
|
|
|
@ -35,9 +35,9 @@ import bisq.core.support.messages.ChatMessage;
|
|||
import bisq.core.support.messages.SupportMessage;
|
||||
import bisq.core.trade.ClosedTradableManager;
|
||||
import bisq.core.trade.Contract;
|
||||
import bisq.core.trade.HavenoUtils;
|
||||
import bisq.core.trade.Trade;
|
||||
import bisq.core.trade.TradeManager;
|
||||
import bisq.core.util.ParsingUtils;
|
||||
|
||||
import bisq.network.p2p.AckMessageSourceType;
|
||||
import bisq.network.p2p.NodeAddress;
|
||||
|
@ -318,12 +318,12 @@ public final class ArbitrationManager extends DisputeManager<ArbitrationDisputeL
|
|||
BigInteger destinationSum = (buyerPayoutDestination == null ? BigInteger.ZERO : buyerPayoutDestination.getAmount()).add(sellerPayoutDestination == null ? BigInteger.ZERO : sellerPayoutDestination.getAmount());
|
||||
if (!arbitratorSignedPayoutTx.getOutputSum().equals(destinationSum.add(arbitratorSignedPayoutTx.getChangeAmount()))) throw new RuntimeException("Sum of outputs != destination amounts + change amount");
|
||||
|
||||
// TODO (woodser): verify fee is reasonable (e.g. within 2x of fee estimate tx)
|
||||
// TODO: verify miner fee is within expected range
|
||||
|
||||
// verify winner and loser payout amounts
|
||||
BigInteger txCost = arbitratorSignedPayoutTx.getFee().add(arbitratorSignedPayoutTx.getChangeAmount()); // fee + lost dust change
|
||||
BigInteger expectedWinnerAmount = ParsingUtils.coinToAtomicUnits(disputeResult.getWinner() == Winner.BUYER ? disputeResult.getBuyerPayoutAmount() : disputeResult.getSellerPayoutAmount());
|
||||
BigInteger expectedLoserAmount = ParsingUtils.coinToAtomicUnits(disputeResult.getWinner() == Winner.BUYER ? disputeResult.getSellerPayoutAmount() : disputeResult.getBuyerPayoutAmount());
|
||||
BigInteger expectedWinnerAmount = HavenoUtils.coinToAtomicUnits(disputeResult.getWinner() == Winner.BUYER ? disputeResult.getBuyerPayoutAmount() : disputeResult.getSellerPayoutAmount());
|
||||
BigInteger expectedLoserAmount = HavenoUtils.coinToAtomicUnits(disputeResult.getWinner() == Winner.BUYER ? disputeResult.getSellerPayoutAmount() : disputeResult.getBuyerPayoutAmount());
|
||||
if (expectedLoserAmount.equals(BigInteger.ZERO)) expectedWinnerAmount = expectedWinnerAmount.subtract(txCost); // winner only pays tx cost if loser gets 0
|
||||
else expectedLoserAmount = expectedLoserAmount.subtract(txCost); // loser pays tx cost
|
||||
BigInteger actualWinnerAmount = disputeResult.getWinner() == Winner.BUYER ? buyerPayoutDestination.getAmount() : sellerPayoutDestination.getAmount();
|
||||
|
|
|
@ -29,6 +29,8 @@ import bisq.core.trade.messages.PaymentSentMessage;
|
|||
import bisq.core.util.JsonUtil;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.math.BigInteger;
|
||||
import java.net.URI;
|
||||
import java.util.Collection;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
|
@ -36,6 +38,8 @@ import java.util.concurrent.ExecutorService;
|
|||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.bitcoinj.core.Coin;
|
||||
|
||||
import com.google.common.base.CaseFormat;
|
||||
import com.google.common.base.Charsets;
|
||||
|
||||
|
@ -48,6 +52,50 @@ public class HavenoUtils {
|
|||
public static final String LOOPBACK_HOST = "127.0.0.1"; // local loopback address to host Monero node
|
||||
public static final String LOCALHOST = "localhost";
|
||||
|
||||
// multipliers to convert units
|
||||
private static BigInteger CENTINEROS_AU_MULTIPLIER = new BigInteger("10000");
|
||||
private static BigInteger XMR_AU_MULTIPLIER = new BigInteger("1000000000000");
|
||||
|
||||
public static BigInteger coinToAtomicUnits(Coin coin) {
|
||||
return centinerosToAtomicUnits(coin.value);
|
||||
}
|
||||
|
||||
public static double coinToXmr(Coin coin) {
|
||||
return atomicUnitsToXmr(coinToAtomicUnits(coin));
|
||||
}
|
||||
|
||||
public static BigInteger centinerosToAtomicUnits(long centineros) {
|
||||
return BigInteger.valueOf(centineros).multiply(CENTINEROS_AU_MULTIPLIER);
|
||||
}
|
||||
|
||||
public static double centinerosToXmr(long centineros) {
|
||||
return atomicUnitsToXmr(centinerosToAtomicUnits(centineros));
|
||||
}
|
||||
|
||||
public static long atomicUnitsToCentineros(long atomicUnits) { // TODO: atomic units should be BigInteger; remove this?
|
||||
return atomicUnits / CENTINEROS_AU_MULTIPLIER.longValue();
|
||||
}
|
||||
|
||||
public static long atomicUnitsToCentineros(BigInteger atomicUnits) {
|
||||
return atomicUnits.divide(CENTINEROS_AU_MULTIPLIER).longValueExact();
|
||||
}
|
||||
|
||||
public static Coin atomicUnitsToCoin(BigInteger atomicUnits) {
|
||||
return Coin.valueOf(atomicUnitsToCentineros(atomicUnits));
|
||||
}
|
||||
|
||||
public static double atomicUnitsToXmr(BigInteger atomicUnits) {
|
||||
return new BigDecimal(atomicUnits).divide(new BigDecimal(XMR_AU_MULTIPLIER)).doubleValue();
|
||||
}
|
||||
|
||||
public static BigInteger xmrToAtomicUnits(double xmr) {
|
||||
return BigDecimal.valueOf(xmr).multiply(new BigDecimal(XMR_AU_MULTIPLIER)).toBigInteger();
|
||||
}
|
||||
|
||||
public static long xmrToCentineros(double xmr) {
|
||||
return atomicUnitsToCentineros(xmrToAtomicUnits(xmr));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get address to collect trade fees.
|
||||
*
|
||||
|
|
|
@ -710,7 +710,7 @@ public abstract class Trade implements Tradable, Model {
|
|||
Preconditions.checkNotNull(buyerPayoutAddress, "Buyer payout address must not be null");
|
||||
BigInteger sellerDepositAmount = multisigWallet.getTx(this.getSeller().getDepositTxHash()).getIncomingAmount();
|
||||
BigInteger buyerDepositAmount = multisigWallet.getTx(this.getBuyer().getDepositTxHash()).getIncomingAmount();
|
||||
BigInteger tradeAmount = ParsingUtils.coinToAtomicUnits(this.getAmount());
|
||||
BigInteger tradeAmount = HavenoUtils.coinToAtomicUnits(this.getAmount());
|
||||
BigInteger buyerPayoutAmount = buyerDepositAmount.add(tradeAmount);
|
||||
BigInteger sellerPayoutAmount = sellerDepositAmount.subtract(tradeAmount);
|
||||
|
||||
|
@ -763,7 +763,7 @@ public abstract class Trade implements Tradable, Model {
|
|||
Contract contract = getContract();
|
||||
BigInteger sellerDepositAmount = multisigWallet.getTx(getSeller().getDepositTxHash()).getIncomingAmount(); // TODO (woodser): redundancy of processModel.getPreparedDepositTxId() vs this.getDepositTxId() necessary or avoidable?
|
||||
BigInteger buyerDepositAmount = multisigWallet.getTx(getBuyer().getDepositTxHash()).getIncomingAmount();
|
||||
BigInteger tradeAmount = ParsingUtils.coinToAtomicUnits(getAmount());
|
||||
BigInteger tradeAmount = HavenoUtils.coinToAtomicUnits(getAmount());
|
||||
|
||||
// describe payout tx
|
||||
MoneroTxSet describedTxSet = multisigWallet.describeTxSet(new MoneroTxSet().setMultisigTxHex(payoutTxHex));
|
||||
|
|
|
@ -24,11 +24,11 @@ import bisq.common.crypto.Sig;
|
|||
import bisq.common.taskrunner.TaskRunner;
|
||||
import bisq.core.offer.Offer;
|
||||
import bisq.core.offer.OfferDirection;
|
||||
import bisq.core.trade.HavenoUtils;
|
||||
import bisq.core.trade.Trade;
|
||||
import bisq.core.trade.messages.DepositRequest;
|
||||
import bisq.core.trade.messages.DepositResponse;
|
||||
import bisq.core.trade.protocol.TradingPeer;
|
||||
import bisq.core.util.ParsingUtils;
|
||||
import bisq.network.p2p.NodeAddress;
|
||||
import bisq.network.p2p.SendDirectMessageListener;
|
||||
import common.utils.JsonUtils;
|
||||
|
@ -37,6 +37,9 @@ import java.math.BigInteger;
|
|||
import java.util.Arrays;
|
||||
import java.util.Date;
|
||||
import java.util.UUID;
|
||||
|
||||
import org.bitcoinj.core.Coin;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import monero.daemon.MoneroDaemon;
|
||||
import monero.daemon.model.MoneroSubmitTxResult;
|
||||
|
@ -70,28 +73,30 @@ public class ArbitratorProcessDepositRequest extends TradeTask {
|
|||
// set peer's signature
|
||||
peer.setContractSignature(signature);
|
||||
|
||||
// collect expected values of deposit tx
|
||||
// collect expected values
|
||||
Offer offer = trade.getOffer();
|
||||
boolean isFromTaker = request.getSenderNodeAddress().equals(trade.getTaker().getNodeAddress());
|
||||
boolean isFromBuyer = isFromTaker ? offer.getDirection() == OfferDirection.SELL : offer.getDirection() == OfferDirection.BUY;
|
||||
BigInteger depositAmount = ParsingUtils.coinToAtomicUnits(isFromBuyer ? offer.getBuyerSecurityDeposit() : offer.getAmount().add(offer.getSellerSecurityDeposit()));
|
||||
BigInteger peerAmount = HavenoUtils.coinToAtomicUnits(isFromBuyer ? Coin.ZERO : offer.getAmount());
|
||||
BigInteger securityDeposit = HavenoUtils.coinToAtomicUnits(isFromBuyer ? offer.getBuyerSecurityDeposit() : offer.getSellerSecurityDeposit());
|
||||
String depositAddress = processModel.getMultisigAddress();
|
||||
BigInteger tradeFee;
|
||||
TradingPeer trader = trade.getTradingPeer(request.getSenderNodeAddress());
|
||||
if (trader == processModel.getMaker()) tradeFee = ParsingUtils.coinToAtomicUnits(trade.getOffer().getMakerFee());
|
||||
else if (trader == processModel.getTaker()) tradeFee = ParsingUtils.coinToAtomicUnits(trade.getTakerFee());
|
||||
if (trader == processModel.getMaker()) tradeFee = HavenoUtils.coinToAtomicUnits(trade.getOffer().getMakerFee());
|
||||
else if (trader == processModel.getTaker()) tradeFee = HavenoUtils.coinToAtomicUnits(trade.getTakerFee());
|
||||
else throw new RuntimeException("DepositRequest is not from maker or taker");
|
||||
|
||||
// verify deposit tx
|
||||
try {
|
||||
trade.getXmrWalletService().verifyTradeTx(depositAddress,
|
||||
depositAmount,
|
||||
trade.getXmrWalletService().verifyTradeTx(
|
||||
tradeFee,
|
||||
peerAmount,
|
||||
securityDeposit,
|
||||
depositAddress,
|
||||
trader.getDepositTxHash(),
|
||||
request.getDepositTxHex(),
|
||||
request.getDepositTxKey(),
|
||||
null,
|
||||
false);
|
||||
null);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Error processing deposit tx from " + (isFromTaker ? "taker " : "maker ") + request.getSenderNodeAddress() + ", offerId=" + offer.getId() + ": " + e.getMessage());
|
||||
}
|
||||
|
|
|
@ -20,11 +20,14 @@ package bisq.core.trade.protocol.tasks;
|
|||
import bisq.common.taskrunner.TaskRunner;
|
||||
import bisq.core.offer.Offer;
|
||||
import bisq.core.offer.OfferDirection;
|
||||
import bisq.core.trade.HavenoUtils;
|
||||
import bisq.core.trade.Trade;
|
||||
import bisq.core.trade.messages.InitTradeRequest;
|
||||
import bisq.core.trade.protocol.TradingPeer;
|
||||
import bisq.core.util.ParsingUtils;
|
||||
import java.math.BigInteger;
|
||||
|
||||
import org.bitcoinj.core.Coin;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
/**
|
||||
|
@ -52,18 +55,19 @@ public class ArbitratorProcessReserveTx extends TradeTask {
|
|||
// TODO (woodser): if signer online, should never be called by maker
|
||||
|
||||
// process reserve tx with expected terms
|
||||
BigInteger tradeFee = ParsingUtils.coinToAtomicUnits(isFromTaker ? trade.getTakerFee() : offer.getMakerFee());
|
||||
BigInteger depositAmount = ParsingUtils.coinToAtomicUnits(isFromBuyer ? offer.getBuyerSecurityDeposit() : offer.getAmount().add(offer.getSellerSecurityDeposit()));
|
||||
BigInteger tradeFee = HavenoUtils.coinToAtomicUnits(isFromTaker ? trade.getTakerFee() : offer.getMakerFee());
|
||||
BigInteger peerAmount = HavenoUtils.coinToAtomicUnits(isFromBuyer ? Coin.ZERO : offer.getAmount());
|
||||
BigInteger securityDeposit = HavenoUtils.coinToAtomicUnits(isFromBuyer ? offer.getBuyerSecurityDeposit() : offer.getSellerSecurityDeposit());
|
||||
try {
|
||||
trade.getXmrWalletService().verifyTradeTx(
|
||||
request.getPayoutAddress(),
|
||||
depositAmount,
|
||||
tradeFee,
|
||||
peerAmount,
|
||||
securityDeposit,
|
||||
request.getPayoutAddress(),
|
||||
request.getReserveTxHash(),
|
||||
request.getReserveTxHex(),
|
||||
request.getReserveTxKey(),
|
||||
null,
|
||||
true);
|
||||
null);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Error processing reserve tx from " + (isFromTaker ? "taker " : "maker ") + request.getSenderNodeAddress() + ", offerId=" + offer.getId() + ": " + e.getMessage());
|
||||
}
|
||||
|
|
|
@ -19,11 +19,15 @@ package bisq.core.trade.protocol.tasks;
|
|||
|
||||
import bisq.common.taskrunner.TaskRunner;
|
||||
import bisq.core.btc.model.XmrAddressEntry;
|
||||
import bisq.core.offer.OfferDirection;
|
||||
import bisq.core.trade.HavenoUtils;
|
||||
import bisq.core.trade.Trade;
|
||||
import bisq.core.util.ParsingUtils;
|
||||
import java.math.BigInteger;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.bitcoinj.core.Coin;
|
||||
|
||||
import monero.daemon.model.MoneroOutput;
|
||||
import monero.wallet.model.MoneroTxWallet;
|
||||
|
||||
|
@ -38,11 +42,12 @@ public class TakerReserveTradeFunds extends TradeTask {
|
|||
try {
|
||||
runInterceptHook();
|
||||
|
||||
// create reserve tx without padding
|
||||
// create reserve tx
|
||||
BigInteger takerFee = HavenoUtils.coinToAtomicUnits(trade.getTakerFee());
|
||||
BigInteger peerAmount = HavenoUtils.coinToAtomicUnits(trade.getOffer().getDirection() == OfferDirection.BUY ? trade.getOffer().getAmount() : Coin.ZERO);
|
||||
BigInteger securityDeposit = HavenoUtils.coinToAtomicUnits(trade.getOffer().getDirection() == OfferDirection.BUY ? trade.getOffer().getSellerSecurityDeposit() : trade.getOffer().getBuyerSecurityDeposit());
|
||||
String returnAddress = model.getXmrWalletService().getOrCreateAddressEntry(trade.getOffer().getId(), XmrAddressEntry.Context.TRADE_PAYOUT).getAddressString();
|
||||
BigInteger takerFee = ParsingUtils.coinToAtomicUnits(trade.getTakerFee());
|
||||
BigInteger depositAmount = ParsingUtils.centinerosToAtomicUnits(processModel.getFundsNeededForTradeAsLong());
|
||||
MoneroTxWallet reserveTx = model.getXmrWalletService().createReserveTx(takerFee, returnAddress, depositAmount, true);
|
||||
MoneroTxWallet reserveTx = model.getXmrWalletService().createReserveTx(takerFee, peerAmount, securityDeposit, returnAddress);
|
||||
|
||||
// collect reserved key images
|
||||
List<String> reservedKeyImages = new ArrayList<String>();
|
||||
|
@ -51,7 +56,7 @@ public class TakerReserveTradeFunds extends TradeTask {
|
|||
// save process state
|
||||
processModel.setReserveTx(reserveTx);
|
||||
processModel.getTaker().setReserveTxKeyImages(reservedKeyImages);
|
||||
|
||||
processModel.getTradeManager().requestPersistence();
|
||||
complete();
|
||||
} catch (Throwable t) {
|
||||
trade.setErrorMessage("An error occurred.\n" +
|
||||
|
|
|
@ -9,58 +9,12 @@ import org.bitcoinj.core.Coin;
|
|||
import org.bitcoinj.utils.MonetaryFormat;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import java.math.BigDecimal;
|
||||
import java.math.BigInteger;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
@Slf4j
|
||||
public class ParsingUtils {
|
||||
|
||||
// multipliers to convert units
|
||||
private static BigInteger CENTINEROS_AU_MULTIPLIER = new BigInteger("10000");
|
||||
private static BigInteger XMR_AU_MULTIPLIER = new BigInteger("1000000000000");
|
||||
|
||||
public static BigInteger coinToAtomicUnits(Coin coin) {
|
||||
return centinerosToAtomicUnits(coin.value);
|
||||
}
|
||||
|
||||
public static double coinToXmr(Coin coin) {
|
||||
return atomicUnitsToXmr(coinToAtomicUnits(coin));
|
||||
}
|
||||
|
||||
public static BigInteger centinerosToAtomicUnits(long centineros) {
|
||||
return BigInteger.valueOf(centineros).multiply(ParsingUtils.CENTINEROS_AU_MULTIPLIER);
|
||||
}
|
||||
|
||||
public static double centinerosToXmr(long centineros) {
|
||||
return atomicUnitsToXmr(centinerosToAtomicUnits(centineros));
|
||||
}
|
||||
|
||||
public static long atomicUnitsToCentineros(long atomicUnits) { // TODO: atomic units should be BigInteger; remove this?
|
||||
return atomicUnits / CENTINEROS_AU_MULTIPLIER.longValue();
|
||||
}
|
||||
|
||||
public static long atomicUnitsToCentineros(BigInteger atomicUnits) {
|
||||
return atomicUnits.divide(CENTINEROS_AU_MULTIPLIER).longValueExact();
|
||||
}
|
||||
|
||||
public static Coin atomicUnitsToCoin(BigInteger atomicUnits) {
|
||||
return Coin.valueOf(atomicUnitsToCentineros(atomicUnits));
|
||||
}
|
||||
|
||||
public static double atomicUnitsToXmr(BigInteger atomicUnits) {
|
||||
return new BigDecimal(atomicUnits).divide(new BigDecimal(XMR_AU_MULTIPLIER)).doubleValue();
|
||||
}
|
||||
|
||||
public static BigInteger xmrToAtomicUnits(double xmr) {
|
||||
return BigDecimal.valueOf(xmr).multiply(new BigDecimal(XMR_AU_MULTIPLIER)).toBigInteger();
|
||||
}
|
||||
|
||||
public static long xmrToCentineros(double xmr) {
|
||||
return atomicUnitsToCentineros(xmrToAtomicUnits(xmr));
|
||||
}
|
||||
|
||||
public static Coin parseToCoin(String input, CoinFormatter coinFormatter) {
|
||||
return parseToCoin(input, coinFormatter.getMonetaryFormat());
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue