verify maker and taker fees

This commit is contained in:
woodser 2022-12-26 13:55:35 +00:00
parent eae3060c63
commit 2d7654b8d7
21 changed files with 102 additions and 150 deletions

View File

@ -305,13 +305,13 @@ public class XmrWalletService {
*
* @param returnAddress return address for reserved funds
* @param tradeFee trade fee
* @param peerAmount amount to give peer
* @param sendAmount amount to give peer
* @param securityDeposit security deposit amount
* @return a transaction to reserve a trade
*/
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);
public MoneroTxWallet createReserveTx(BigInteger tradeFee, BigInteger sendAmount, BigInteger securityDeposit, String returnAddress) {
log.info("Creating reserve tx with fee={}, sendAmount={}, securityDeposit={}", tradeFee, sendAmount, securityDeposit);
return createTradeTx(tradeFee, sendAmount, securityDeposit, returnAddress);
}
/**
@ -324,7 +324,7 @@ public class XmrWalletService {
Offer offer = trade.getProcessModel().getOffer();
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 sendAmount = HavenoUtils.coinToAtomicUnits(trade instanceof BuyerTrade ? Coin.ZERO : offer.getAmount());
BigInteger securityDeposit = HavenoUtils.coinToAtomicUnits(trade instanceof BuyerTrade ? offer.getBuyerSecurityDeposit() : offer.getSellerSecurityDeposit());
// thaw reserved outputs then create deposit tx
@ -336,12 +336,12 @@ public class XmrWalletService {
thawOutputs(trade.getSelf().getReserveTxKeyImages());
}
log.info("Creating deposit tx with fee={}, peerAmount={}, securityDeposit={}", tradeFee, peerAmount, securityDeposit);
return createTradeTx(tradeFee, peerAmount, securityDeposit, multisigAddress);
log.info("Creating deposit tx with fee={}, sendAmount={}, securityDeposit={}", tradeFee, sendAmount, securityDeposit);
return createTradeTx(tradeFee, sendAmount, securityDeposit, multisigAddress);
}
}
private MoneroTxWallet createTradeTx(BigInteger tradeFee, BigInteger peerAmount, BigInteger securityDeposit, String address) {
private MoneroTxWallet createTradeTx(BigInteger tradeFee, BigInteger sendAmount, BigInteger securityDeposit, String address) {
MoneroWallet wallet = getWallet();
synchronized (wallet) {
@ -349,7 +349,7 @@ public class XmrWalletService {
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);
BigInteger maxAmount = sendAmount.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();
@ -379,7 +379,7 @@ public class XmrWalletService {
* The transaction is submitted to the pool then flushed without relaying.
*
* @param tradeFee trade fee
* @param peerAmount amount to give peer
* @param sendAmount amount to give peer
* @param securityDeposit security deposit amount
* @param address expected destination address for the deposit amount
* @param txHash transaction hash
@ -387,7 +387,7 @@ public class XmrWalletService {
* @param txKey transaction key
* @param keyImages expected key images of inputs, ignored if null
*/
public void verifyTradeTx(BigInteger tradeFee, BigInteger peerAmount, BigInteger securityDeposit, String address, String txHash, String txHex, String txKey, List<String> keyImages) {
public void verifyTradeTx(BigInteger tradeFee, BigInteger sendAmount, BigInteger securityDeposit, String address, String txHash, String txHex, String txKey, List<String> keyImages) {
MoneroDaemonRpc daemon = getDaemon();
MoneroWallet wallet = getWallet();
try {
@ -426,7 +426,7 @@ public class XmrWalletService {
// verify deposit amount
check = wallet.checkTxKey(txHash, txKey, address);
if (!check.isGood()) throw new RuntimeException("Invalid proof of deposit amount");
BigInteger minAmount = new BigDecimal(peerAmount.add(securityDeposit)).multiply(new BigDecimal(1.0 - SECURITY_DEPOSIT_TOLERANCE)).toBigInteger();
BigInteger minAmount = new BigDecimal(sendAmount.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 {

View File

@ -27,6 +27,7 @@ import bisq.core.payment.PaymentAccountUtil;
import bisq.core.provider.price.MarketPrice;
import bisq.core.provider.price.PriceFeedService;
import bisq.core.support.dispute.arbitration.arbitrator.ArbitratorManager;
import bisq.core.trade.HavenoUtils;
import bisq.core.trade.statistics.TradeStatisticsManager;
import bisq.core.user.Preferences;
import bisq.core.user.User;
@ -155,7 +156,7 @@ public class CreateOfferService {
String bankId = PaymentAccountUtil.getBankId(paymentAccount);
List<String> acceptedBanks = PaymentAccountUtil.getAcceptedBanks(paymentAccount);
double sellerSecurityDeposit = getSellerSecurityDepositAsDouble(buyerSecurityDepositAsDouble);
Coin makerFeeAsCoin = offerUtil.getMakerFee(amount);
Coin makerFeeAsCoin = HavenoUtils.getMakerFee(amount);
Coin buyerSecurityDepositAsCoin = getBuyerSecurityDeposit(amount, buyerSecurityDepositAsDouble);
Coin sellerSecurityDepositAsCoin = getSellerSecurityDeposit(amount, sellerSecurityDeposit);
long maxTradeLimit = offerUtil.getMaxTradeLimit(paymentAccount, currencyCode, direction);

View File

@ -29,12 +29,10 @@ import bisq.core.payment.F2FAccount;
import bisq.core.payment.PaymentAccount;
import bisq.core.provider.price.MarketPrice;
import bisq.core.provider.price.PriceFeedService;
import bisq.core.trade.HavenoUtils;
import bisq.core.trade.statistics.ReferralIdService;
import bisq.core.user.AutoConfirmSettings;
import bisq.core.user.Preferences;
import bisq.core.util.coin.CoinFormatter;
import bisq.core.util.coin.CoinUtil;
import bisq.network.p2p.P2PService;
@ -58,8 +56,6 @@ import java.util.UUID;
import lombok.extern.slf4j.Slf4j;
import javax.annotation.Nullable;
import static bisq.common.util.MathUtils.roundDoubleToLong;
import static bisq.common.util.MathUtils.scaleUpByPowerOf10;
import static bisq.core.btc.wallet.Restrictions.getMaxBuyerSecurityDepositAsPercent;
@ -163,41 +159,6 @@ public class OfferUtil {
return MathUtils.roundDouble(manualPrice / marketPrice, 4);
}
/**
* Returns the makerFee as Coin, this can be priced in BTC.
*
* @param amount the amount of BTC to trade
* @return the maker fee for the given trade amount, or {@code null} if the amount
* is {@code null}
*/
@Nullable
public Coin getMakerFee(@Nullable Coin amount) {
return CoinUtil.getMakerFee(amount);
}
public Coin getTxFeeByVsize(Coin txFeePerVbyteFromFeeService, int vsizeInVbytes) {
return txFeePerVbyteFromFeeService.multiply(getAverageTakerFeeTxVsize(vsizeInVbytes));
}
// We use the sum of the size of the trade fee and the deposit tx to get an average.
// Miners will take the trade fee tx if the total fee of both dependent txs are good
// enough. With that we avoid that we overpay in case that the trade fee has many
// inputs and we would apply that fee for the other 2 txs as well. We still might
// overpay a bit for the payout tx.
public int getAverageTakerFeeTxVsize(int txVsize) {
return (txVsize + 233) / 2;
}
@Nullable
public Coin getTakerFee(@Nullable Coin amount) {
if (amount != null) {
Coin feePerBtc = CoinUtil.getFeePerBtc(HavenoUtils.getTakerFeePerBtc(), amount);
return CoinUtil.maxCoin(feePerBtc, HavenoUtils.getMinTakerFee());
} else {
return null;
}
}
public boolean isBlockChainPaymentMethod(Offer offer) {
return offer != null && offer.getPaymentMethod().isBlockchain();
}

View File

@ -856,15 +856,23 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
sendAckMessage(request.getClass(), peer, request.getPubKeyRing(), request.getOfferId(), request.getUid(), false, errorMessage);
return;
}
// verify maker's trade fee
Offer offer = new Offer(request.getOfferPayload());
BigInteger tradeFee = HavenoUtils.coinToAtomicUnits(HavenoUtils.getMakerFee(offer.getAmount()));
if (!tradeFee.equals(HavenoUtils.coinToAtomicUnits(offer.getMakerFee()))) {
errorMessage = "Wrong trade fee for offer " + request.offerId;
log.info(errorMessage);
sendAckMessage(request.getClass(), peer, request.getPubKeyRing(), request.getOfferId(), request.getUid(), false, errorMessage);
return;
}
// verify maker's reserve tx (double spend, trade fee, trade amount, mining fee)
Offer offer = new Offer(request.getOfferPayload());
BigInteger tradeFee = HavenoUtils.coinToAtomicUnits(offer.getMakerFee());
BigInteger peerAmount = HavenoUtils.coinToAtomicUnits(offer.getDirection() == OfferDirection.BUY ? Coin.ZERO : offer.getAmount());
BigInteger sendAmount = HavenoUtils.coinToAtomicUnits(offer.getDirection() == OfferDirection.BUY ? Coin.ZERO : offer.getAmount());
BigInteger securityDeposit = HavenoUtils.coinToAtomicUnits(offer.getDirection() == OfferDirection.BUY ? offer.getBuyerSecurityDeposit() : offer.getSellerSecurityDeposit());
xmrWalletService.verifyTradeTx(
tradeFee,
peerAmount,
sendAmount,
securityDeposit,
request.getPayoutAddress(),
request.getReserveTxHash(),

View File

@ -24,6 +24,7 @@ import bisq.core.offer.Offer;
import bisq.core.offer.OfferUtil;
import bisq.core.offer.availability.OfferAvailabilityModel;
import bisq.core.offer.messages.OfferAvailabilityRequest;
import bisq.core.trade.HavenoUtils;
import bisq.core.trade.messages.InitTradeRequest;
import bisq.core.user.User;
import bisq.network.p2p.P2PService;
@ -55,7 +56,6 @@ public class SendOfferAvailabilityRequest extends Task<OfferAvailabilityModel> {
User user = model.getUser();
P2PService p2PService = model.getP2PService();
XmrWalletService walletService = model.getXmrWalletService();
OfferUtil offerUtil = model.getOfferUtil();
String paymentAccountId = model.getPaymentAccountId();
String paymentMethodId = user.getPaymentAccount(paymentAccountId).getPaymentAccountPayload().getPaymentMethodId();
String payoutAddress = walletService.getOrCreateAddressEntry(offer.getId(), XmrAddressEntry.Context.TRADE_PAYOUT).getAddressString(); // reserve new payout address
@ -74,7 +74,7 @@ public class SendOfferAvailabilityRequest extends Task<OfferAvailabilityModel> {
p2PService.getKeyRing().getPubKeyRing(),
offer.getAmount().value,
price.getValue(),
offerUtil.getTakerFee(offer.getAmount()).value,
HavenoUtils.getTakerFee(offer.getAmount()).value,
user.getAccountId(),
paymentAccountId,
paymentMethodId,

View File

@ -52,10 +52,10 @@ public class MakerReserveOfferFunds extends Task<PlaceOfferModel> {
// create reserve tx
BigInteger makerFee = HavenoUtils.coinToAtomicUnits(offer.getMakerFee());
BigInteger peerAmount = HavenoUtils.coinToAtomicUnits(offer.getDirection() == OfferDirection.BUY ? Coin.ZERO : offer.getAmount());
BigInteger sendAmount = 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();
MoneroTxWallet reserveTx = model.getXmrWalletService().createReserveTx(makerFee, peerAmount, securityDeposit, returnAddress);
MoneroTxWallet reserveTx = model.getXmrWalletService().createReserveTx(makerFee, sendAmount, securityDeposit, returnAddress);
// collect reserved key images
List<String> reservedKeyImages = new ArrayList<String>();

View File

@ -28,7 +28,7 @@ import bisq.core.offer.OfferUtil;
import bisq.core.payment.PaymentAccount;
import bisq.core.payment.payload.PaymentMethod;
import bisq.core.provider.price.PriceFeedService;
import bisq.core.trade.HavenoUtils;
import bisq.common.taskrunner.Model;
import org.bitcoinj.core.Coin;
@ -111,7 +111,7 @@ public class TakeOfferModel implements Model {
this.securityDeposit = offer.getDirection() == SELL
? offer.getBuyerSecurityDeposit()
: offer.getSellerSecurityDeposit();
this.takerFee = offerUtil.getTakerFee(amount);
this.takerFee = HavenoUtils.getTakerFee(amount);
calculateVolume();
calculateTotalToPay();

View File

@ -32,8 +32,6 @@ import java.util.UUID;
import lombok.extern.slf4j.Slf4j;
import javax.annotation.Nullable;
@Slf4j
public final class BuyerAsMakerTrade extends BuyerTrade implements MakerTrade {

View File

@ -28,6 +28,7 @@ import bisq.core.trade.messages.PaymentReceivedMessage;
import bisq.core.trade.messages.PaymentSentMessage;
import bisq.core.util.JsonUtil;
import bisq.core.util.ParsingUtils;
import bisq.core.util.coin.CoinUtil;
import lombok.extern.slf4j.Slf4j;
import java.math.BigDecimal;
@ -39,6 +40,8 @@ import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import javax.annotation.Nullable;
import org.bitcoinj.core.Coin;
import org.bitcoinj.utils.MonetaryFormat;
import com.google.common.base.CaseFormat;
@ -99,8 +102,27 @@ public class HavenoUtils {
private static final MonetaryFormat xmrCoinFormat = Config.baseCurrencyNetworkParameters().getMonetaryFormat();
@Nullable
public static Coin getMakerFee(@Nullable Coin amount) {
if (amount != null) {
Coin feePerXmr = getFeePerXmr(HavenoUtils.getMakerFeePerXmr(), amount);
return CoinUtil.maxCoin(feePerXmr, HavenoUtils.getMinMakerFee());
} else {
return null;
}
}
public static Coin getMakerFeePerBtc() {
@Nullable
public static Coin getTakerFee(@Nullable Coin amount) {
if (amount != null) {
Coin feePerXmr = HavenoUtils.getFeePerXmr(HavenoUtils.getTakerFeePerXmr(), amount);
return CoinUtil.maxCoin(feePerXmr, HavenoUtils.getMinTakerFee());
} else {
return null;
}
}
private static Coin getMakerFeePerXmr() {
return ParsingUtils.parseToCoin("0.001", xmrCoinFormat);
}
@ -108,7 +130,7 @@ public class HavenoUtils {
return ParsingUtils.parseToCoin("0.00005", xmrCoinFormat);
}
public static Coin getTakerFeePerBtc() {
private static Coin getTakerFeePerXmr() {
return ParsingUtils.parseToCoin("0.003", xmrCoinFormat);
}
@ -116,6 +138,14 @@ public class HavenoUtils {
return ParsingUtils.parseToCoin("0.00005", xmrCoinFormat);
}
public static Coin getFeePerXmr(Coin feePerXmr, Coin amount) {
double feePerBtcAsDouble = feePerXmr != null ? (double) feePerXmr.value : 0;
double amountAsDouble = amount != null ? (double) amount.value : 0;
double btcAsDouble = (double) Coin.COIN.value;
double fact = amountAsDouble / btcAsDouble;
return Coin.valueOf(Math.round(feePerBtcAsDouble * fact));
}
/**
* Get address to collect trade fees.
*

View File

@ -55,7 +55,6 @@ import bisq.core.trade.statistics.ReferralIdService;
import bisq.core.trade.statistics.TradeStatisticsManager;
import bisq.core.user.User;
import bisq.core.util.Validator;
import bisq.core.util.coin.CoinUtil;
import bisq.network.p2p.BootstrapListener;
import bisq.network.p2p.DecryptedDirectMessageListener;
import bisq.network.p2p.DecryptedMessageWithPubKey;
@ -456,9 +455,8 @@ public class TradeManager implements PersistedDataHost, DecryptedDirectMessageLi
return;
}
// compute expected taker fee
Coin feePerBtc = CoinUtil.getFeePerBtc(HavenoUtils.getTakerFeePerBtc(), Coin.valueOf(offer.getOfferPayload().getAmount()));
Coin takerFee = CoinUtil.maxCoin(feePerBtc, HavenoUtils.getMinTakerFee());
// get expected taker fee
Coin takerFee = HavenoUtils.getTakerFee(Coin.valueOf(offer.getOfferPayload().getAmount()));
// create arbitrator trade
trade = new ArbitratorTrade(offer,
@ -522,13 +520,17 @@ public class TradeManager implements PersistedDataHost, DecryptedDirectMessageLi
return;
}
// reserve open offer
openOfferManager.reserveOpenOffer(openOffer); // TODO (woodser): reserve offer if arbitrator? probably. or, arbitrator does not have open offer?
// get expected taker fee
Coin takerFee = HavenoUtils.getTakerFee(Coin.valueOf(offer.getOfferPayload().getAmount()));
Trade trade;
if (offer.isBuyOffer())
trade = new BuyerAsMakerTrade(offer,
Coin.valueOf(offer.getOfferPayload().getAmount()),
Coin.valueOf(offer.getOfferPayload().getMakerFee()), // TODO (woodser): this is maker fee, but Trade calls it taker fee, why not have both?
takerFee,
offer.getOfferPayload().getPrice(),
xmrWalletService,
getNewProcessModel(offer),
@ -539,7 +541,7 @@ public class TradeManager implements PersistedDataHost, DecryptedDirectMessageLi
else
trade = new SellerAsMakerTrade(offer,
Coin.valueOf(offer.getOfferPayload().getAmount()),
Coin.valueOf(offer.getOfferPayload().getMakerFee()),
takerFee,
offer.getOfferPayload().getPrice(),
xmrWalletService,
getNewProcessModel(offer),

View File

@ -75,22 +75,19 @@ public class ArbitratorProcessDepositRequest extends TradeTask {
// 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 peerAmount = HavenoUtils.coinToAtomicUnits(isFromBuyer ? Coin.ZERO : offer.getAmount());
TradingPeer trader = trade.getTradingPeer(request.getSenderNodeAddress());
boolean isFromTaker = trader == trade.getTaker();
boolean isFromBuyer = trader == trade.getBuyer();
BigInteger tradeFee = HavenoUtils.coinToAtomicUnits(isFromTaker ? trade.getTakerFee() : trade.getMakerFee());
BigInteger sendAmount = 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 = 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(
tradeFee,
peerAmount,
sendAmount,
securityDeposit,
depositAddress,
trader.getDepositTxHash(),

View File

@ -54,14 +54,14 @@ public class ArbitratorProcessReserveTx extends TradeTask {
// TODO (woodser): if signer online, should never be called by maker
// process reserve tx with expected terms
BigInteger tradeFee = HavenoUtils.coinToAtomicUnits(isFromTaker ? trade.getTakerFee() : offer.getMakerFee());
BigInteger peerAmount = HavenoUtils.coinToAtomicUnits(isFromBuyer ? Coin.ZERO : offer.getAmount());
// process reserve tx with expected values
BigInteger tradeFee = HavenoUtils.coinToAtomicUnits(isFromTaker ? trade.getTakerFee() : trade.getMakerFee());
BigInteger sendAmount = HavenoUtils.coinToAtomicUnits(isFromBuyer ? Coin.ZERO : offer.getAmount());
BigInteger securityDeposit = HavenoUtils.coinToAtomicUnits(isFromBuyer ? offer.getBuyerSecurityDeposit() : offer.getSellerSecurityDeposit());
try {
trade.getXmrWalletService().verifyTradeTx(
tradeFee,
peerAmount,
sendAmount,
securityDeposit,
request.getPayoutAddress(),
request.getReserveTxHash(),

View File

@ -44,10 +44,10 @@ public class TakerReserveTradeFunds extends TradeTask {
// create reserve tx
BigInteger takerFee = HavenoUtils.coinToAtomicUnits(trade.getTakerFee());
BigInteger peerAmount = HavenoUtils.coinToAtomicUnits(trade.getOffer().getDirection() == OfferDirection.BUY ? trade.getOffer().getAmount() : Coin.ZERO);
BigInteger sendAmount = 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();
MoneroTxWallet reserveTx = model.getXmrWalletService().createReserveTx(takerFee, peerAmount, securityDeposit, returnAddress);
MoneroTxWallet reserveTx = model.getXmrWalletService().createReserveTx(takerFee, sendAmount, securityDeposit, returnAddress);
// collect reserved key images
List<String> reservedKeyImages = new ArrayList<String>();

View File

@ -20,28 +20,17 @@ package bisq.core.util.coin;
import bisq.core.btc.wallet.Restrictions;
import bisq.core.monetary.Price;
import bisq.core.monetary.Volume;
import bisq.core.trade.HavenoUtils;
import bisq.common.util.MathUtils;
import org.bitcoinj.core.Coin;
import com.google.common.annotations.VisibleForTesting;
import javax.annotation.Nullable;
import static bisq.core.util.VolumeUtil.getAdjustedFiatVolume;
import static com.google.common.base.Preconditions.checkArgument;
public class CoinUtil {
// Get the fee per amount
public static Coin getFeePerBtc(Coin feePerBtc, Coin amount) {
double feePerBtcAsDouble = feePerBtc != null ? (double) feePerBtc.value : 0;
double amountAsDouble = amount != null ? (double) amount.value : 0;
double btcAsDouble = (double) Coin.COIN.value;
double fact = amountAsDouble / btcAsDouble;
return Coin.valueOf(Math.round(feePerBtcAsDouble * fact));
}
public static Coin minCoin(Coin a, Coin b) {
return a.compareTo(b) <= 0 ? a : b;
@ -87,23 +76,6 @@ public class CoinUtil {
return Coin.valueOf(Math.round(percent * amountAsDouble));
}
/**
* Calculates the maker fee for the given amount, marketPrice and marketPriceMargin.
*
* @param amount the amount of BTC to trade
* @return the maker fee for the given trade amount, or {@code null} if the amount is {@code null}
*/
@Nullable
public static Coin getMakerFee(@Nullable Coin amount) {
if (amount != null) {
Coin feePerBtc = getFeePerBtc(HavenoUtils.getMakerFeePerBtc(), amount);
return maxCoin(feePerBtc, HavenoUtils.getMinMakerFee());
} else {
return null;
}
}
/**
* Calculate the possibly adjusted amount for {@code amount}, taking into account the
* {@code price} and {@code maxTradeLimit} and {@code factor}.

View File

@ -18,6 +18,7 @@
package bisq.core.util.coin;
import bisq.core.monetary.Price;
import bisq.core.trade.HavenoUtils;
import org.bitcoinj.core.Coin;
@ -30,10 +31,10 @@ public class CoinUtilTest {
@Test
public void testGetFeePerBtc() {
assertEquals(Coin.parseCoin("1"), CoinUtil.getFeePerBtc(Coin.parseCoin("1"), Coin.parseCoin("1")));
assertEquals(Coin.parseCoin("0.1"), CoinUtil.getFeePerBtc(Coin.parseCoin("0.1"), Coin.parseCoin("1")));
assertEquals(Coin.parseCoin("0.01"), CoinUtil.getFeePerBtc(Coin.parseCoin("0.1"), Coin.parseCoin("0.1")));
assertEquals(Coin.parseCoin("0.015"), CoinUtil.getFeePerBtc(Coin.parseCoin("0.3"), Coin.parseCoin("0.05")));
assertEquals(Coin.parseCoin("1"), HavenoUtils.getFeePerXmr(Coin.parseCoin("1"), Coin.parseCoin("1")));
assertEquals(Coin.parseCoin("0.1"), HavenoUtils.getFeePerXmr(Coin.parseCoin("0.1"), Coin.parseCoin("1")));
assertEquals(Coin.parseCoin("0.01"), HavenoUtils.getFeePerXmr(Coin.parseCoin("0.1"), Coin.parseCoin("0.1")));
assertEquals(Coin.parseCoin("0.015"), HavenoUtils.getFeePerXmr(Coin.parseCoin("0.3"), Coin.parseCoin("0.05")));
}
@Test

View File

@ -37,6 +37,7 @@ import bisq.core.offer.OfferUtil;
import bisq.core.offer.OpenOfferManager;
import bisq.core.payment.PaymentAccount;
import bisq.core.provider.price.PriceFeedService;
import bisq.core.trade.HavenoUtils;
import bisq.core.trade.handlers.TransactionResultHandler;
import bisq.core.trade.statistics.TradeStatistics3;
import bisq.core.trade.statistics.TradeStatisticsManager;
@ -674,11 +675,7 @@ public abstract class MutableOfferDataModel extends OfferDataModel {
}
public Coin getMakerFee() {
return offerUtil.getMakerFee(amount.get());
}
public Coin getMakerFeeInBtc() {
return CoinUtil.getMakerFee(amount.get());
return HavenoUtils.getMakerFee(amount.get());
}
boolean canPlaceOffer() {

View File

@ -506,7 +506,7 @@ public abstract class MutableOfferViewModel<M extends MutableOfferDataModel> ext
isTradeFeeVisible.setValue(true);
tradeFee.set(getFormatterForMakerFee().formatCoin(makerFeeAsCoin));
tradeFeeInBtcWithFiat.set(OfferViewModelUtil.getTradeFeeWithFiatEquivalent(offerUtil,
dataModel.getMakerFeeInBtc(),
dataModel.getMakerFee(),
btcFormatter));
}
@ -996,7 +996,7 @@ public abstract class MutableOfferViewModel<M extends MutableOfferDataModel> ext
public String getTradeFee() {
return OfferViewModelUtil.getTradeFeeWithFiatEquivalentAndPercentage(offerUtil,
dataModel.getMakerFeeInBtc(),
dataModel.getMakerFee(),
dataModel.getAmount().get(),
btcFormatter,
HavenoUtils.getMinMakerFee());

View File

@ -434,14 +434,7 @@ class TakeOfferDataModel extends OfferDataModel {
@Nullable
Coin getTakerFee() {
Coin amount = this.amount.get();
if (amount != null) {
// TODO write unit test for that
Coin feePerBtc = CoinUtil.getFeePerBtc(HavenoUtils.getTakerFeePerBtc(), amount);
return CoinUtil.maxCoin(feePerBtc, HavenoUtils.getMinTakerFee());
} else {
return null;
}
return HavenoUtils.getTakerFee(this.amount.get());
}
public void swapTradeToSavings() {
@ -523,8 +516,4 @@ class TakeOfferDataModel extends OfferDataModel {
public boolean isUsingHalCashAccount() {
return paymentAccount.hasPaymentMethodWithId(HAL_CASH_ID);
}
public Coin getTakerFeeInBtc() {
return offerUtil.getTakerFee(amount.get());
}
}

View File

@ -547,7 +547,7 @@ public class TakeOfferView extends ActivatableViewAndModel<AnchorPane, TakeOffer
priceCurrencyLabel.textProperty().bind(createStringBinding(() -> CurrencyUtil.getCounterCurrency(model.dataModel.getCurrencyCode())));
priceAsPercentageLabel.prefWidthProperty().bind(priceCurrencyLabel.widthProperty());
nextButton.disableProperty().bind(model.isNextButtonDisabled);
tradeFeeInBtcLabel.textProperty().bind(model.tradeFeeInBtcWithFiat);
tradeFeeInBtcLabel.textProperty().bind(model.tradeFeeInXmrWithFiat);
tradeFeeDescriptionLabel.textProperty().bind(model.tradeFeeDescription);
tradeFeeInBtcLabel.visibleProperty().bind(model.isTradeFeeVisible);
tradeFeeDescriptionLabel.visibleProperty().bind(model.isTradeFeeVisible);

View File

@ -103,7 +103,7 @@ class TakeOfferViewModel extends ActivatableWithDataModel<TakeOfferDataModel> im
final StringProperty offerWarning = new SimpleStringProperty();
final StringProperty spinnerInfoText = new SimpleStringProperty("");
final StringProperty tradeFee = new SimpleStringProperty();
final StringProperty tradeFeeInBtcWithFiat = new SimpleStringProperty();
final StringProperty tradeFeeInXmrWithFiat = new SimpleStringProperty();
final StringProperty tradeFeeDescription = new SimpleStringProperty();
final BooleanProperty isTradeFeeVisible = new SimpleBooleanProperty(false);
@ -283,8 +283,8 @@ class TakeOfferViewModel extends ActivatableWithDataModel<TakeOfferDataModel> im
isTradeFeeVisible.setValue(true);
tradeFee.set(getFormatterForTakerFee().formatCoin(takerFeeAsCoin));
tradeFeeInBtcWithFiat.set(OfferViewModelUtil.getTradeFeeWithFiatEquivalent(offerUtil,
dataModel.getTakerFeeInBtc(),
tradeFeeInXmrWithFiat.set(OfferViewModelUtil.getTradeFeeWithFiatEquivalent(offerUtil,
dataModel.getTakerFee(),
xmrFormatter));
}
@ -689,7 +689,7 @@ class TakeOfferViewModel extends ActivatableWithDataModel<TakeOfferDataModel> im
public String getTradeFee() {
return OfferViewModelUtil.getTradeFeeWithFiatEquivalentAndPercentage(offerUtil,
dataModel.getTakerFeeInBtc(),
dataModel.getTakerFee(),
dataModel.getAmount().get(),
xmrFormatter,
HavenoUtils.getMinMakerFee());

View File

@ -17,8 +17,6 @@ import bisq.core.trade.statistics.TradeStatisticsManager;
import bisq.core.user.Preferences;
import bisq.core.user.User;
import org.bitcoinj.core.Coin;
import javafx.collections.FXCollections;
import java.util.HashSet;
@ -47,7 +45,7 @@ public class CreateOfferDataModelTest {
Res.setup();
XmrAddressEntry addressEntry = mock(XmrAddressEntry.class);
XmrWalletService btcWalletService = mock(XmrWalletService.class);
XmrWalletService xmrWalletService = mock(XmrWalletService.class);
PriceFeedService priceFeedService = mock(PriceFeedService.class);
CreateOfferService createOfferService = mock(CreateOfferService.class);
preferences = mock(Preferences.class);
@ -55,7 +53,7 @@ public class CreateOfferDataModelTest {
user = mock(User.class);
var tradeStats = mock(TradeStatisticsManager.class);
when(btcWalletService.getOrCreateAddressEntry(anyString(), any())).thenReturn(addressEntry);
when(xmrWalletService.getOrCreateAddressEntry(anyString(), any())).thenReturn(addressEntry);
when(preferences.isUsePercentageBasedPrice()).thenReturn(true);
when(preferences.getBuyerSecurityDepositAsPercent(null)).thenReturn(0.01);
when(createOfferService.getRandomOfferId()).thenReturn(UUID.randomUUID().toString());
@ -64,7 +62,7 @@ public class CreateOfferDataModelTest {
model = new CreateOfferDataModel(createOfferService,
null,
offerUtil,
btcWalletService,
xmrWalletService,
preferences,
user,
null,
@ -91,7 +89,6 @@ public class CreateOfferDataModelTest {
when(user.getPaymentAccounts()).thenReturn(paymentAccounts);
when(preferences.getSelectedPaymentAccountForCreateOffer()).thenReturn(revolutAccount);
when(offerUtil.getMakerFee(any())).thenReturn(Coin.ZERO);
model.initWithData(OfferDirection.BUY, new FiatCurrency("USD"));
assertEquals("USD", model.getTradeCurrencyCode().get());
@ -113,7 +110,6 @@ public class CreateOfferDataModelTest {
when(user.getPaymentAccounts()).thenReturn(paymentAccounts);
when(user.findFirstPaymentAccountWithCurrency(new FiatCurrency("USD"))).thenReturn(zelleAccount);
when(preferences.getSelectedPaymentAccountForCreateOffer()).thenReturn(revolutAccount);
when(offerUtil.getMakerFee(any())).thenReturn(Coin.ZERO);
model.initWithData(OfferDirection.BUY, new FiatCurrency("USD"));
assertEquals("USD", model.getTradeCurrencyCode().get());