use separate fees for crypto and traditional payment methods (#1865)

This commit is contained in:
woodser 2025-07-21 09:58:56 -04:00 committed by GitHub
parent fd2c0f335f
commit ffadc09712
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 42 additions and 14 deletions

View file

@ -205,8 +205,8 @@ public class CreateOfferService {
useMarketBasedPriceValue,
amountAsLong,
minAmountAsLong,
hasBuyerAsTakerWithoutDeposit ? HavenoUtils.MAKER_FEE_FOR_TAKER_WITHOUT_DEPOSIT_PCT : HavenoUtils.MAKER_FEE_PCT,
hasBuyerAsTakerWithoutDeposit ? 0d : HavenoUtils.TAKER_FEE_PCT,
HavenoUtils.getMakerFeePct(currencyCode, hasBuyerAsTakerWithoutDeposit),
HavenoUtils.getTakerFeePct(currencyCode, hasBuyerAsTakerWithoutDeposit),
HavenoUtils.PENALTY_FEE_PCT,
hasBuyerAsTakerWithoutDeposit ? 0d : securityDepositPct, // buyer as taker security deposit is optional for private offers
securityDepositPct,

View file

@ -1595,8 +1595,9 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
if (hasBuyerAsTakerWithoutDeposit) {
// verify maker's trade fee
if (offer.getMakerFeePct() != HavenoUtils.MAKER_FEE_FOR_TAKER_WITHOUT_DEPOSIT_PCT) {
errorMessage = "Wrong maker fee for offer " + request.offerId + ". Expected " + HavenoUtils.MAKER_FEE_FOR_TAKER_WITHOUT_DEPOSIT_PCT + " but got " + offer.getMakerFeePct();
double makerFeePct = HavenoUtils.getMakerFeePct(request.getOfferPayload().getCounterCurrencyCode(), hasBuyerAsTakerWithoutDeposit);
if (offer.getMakerFeePct() != makerFeePct) {
errorMessage = "Wrong maker fee for offer " + request.offerId + ". Expected " + makerFeePct + " but got " + offer.getMakerFeePct();
log.warn(errorMessage);
sendAckMessage(request.getClass(), peer, request.getPubKeyRing(), request.getOfferId(), request.getUid(), false, errorMessage);
return;
@ -1636,16 +1637,18 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
}
// verify maker's trade fee
if (offer.getMakerFeePct() != HavenoUtils.MAKER_FEE_PCT) {
errorMessage = "Wrong maker fee for offer " + request.offerId + ". Expected " + HavenoUtils.MAKER_FEE_PCT + " but got " + offer.getMakerFeePct();
double makerFeePct = HavenoUtils.getMakerFeePct(request.getOfferPayload().getCounterCurrencyCode(), hasBuyerAsTakerWithoutDeposit);
if (offer.getMakerFeePct() != makerFeePct) {
errorMessage = "Wrong maker fee for offer " + request.offerId + ". Expected " + makerFeePct + " but got " + offer.getMakerFeePct();
log.warn(errorMessage);
sendAckMessage(request.getClass(), peer, request.getPubKeyRing(), request.getOfferId(), request.getUid(), false, errorMessage);
return;
}
// verify taker's trade fee
if (offer.getTakerFeePct() != HavenoUtils.TAKER_FEE_PCT) {
errorMessage = "Wrong taker fee for offer " + request.offerId + ". Expected " + HavenoUtils.TAKER_FEE_PCT + " but got " + offer.getTakerFeePct();
double takerFeePct = HavenoUtils.getTakerFeePct(request.getOfferPayload().getCounterCurrencyCode(), hasBuyerAsTakerWithoutDeposit);
if (offer.getTakerFeePct() != takerFeePct) {
errorMessage = "Wrong taker fee for offer " + request.offerId + ". Expected " + takerFeePct + " but got " + offer.getTakerFeePct();
log.warn(errorMessage);
sendAckMessage(request.getClass(), peer, request.getPubKeyRing(), request.getOfferId(), request.getUid(), false, errorMessage);
return;
@ -1685,7 +1688,8 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
}
// verify maker's reserve tx (double spend, trade fee, trade amount, mining fee)
BigInteger maxTradeFee = HavenoUtils.multiply(offer.getAmount(), hasBuyerAsTakerWithoutDeposit ? HavenoUtils.MAKER_FEE_FOR_TAKER_WITHOUT_DEPOSIT_PCT : HavenoUtils.MAKER_FEE_PCT);
double makerFeePct = HavenoUtils.getMakerFeePct(request.getOfferPayload().getCounterCurrencyCode(), hasBuyerAsTakerWithoutDeposit);
BigInteger maxTradeFee = HavenoUtils.multiply(offer.getAmount(), makerFeePct);
BigInteger sendTradeAmount = offer.getDirection() == OfferDirection.BUY ? BigInteger.ZERO : offer.getAmount();
BigInteger securityDeposit = offer.getDirection() == OfferDirection.BUY ? offer.getMaxBuyerSecurityDeposit() : offer.getMaxSellerSecurityDeposit();
BigInteger penaltyFee = HavenoUtils.multiply(securityDeposit, HavenoUtils.PENALTY_FEE_PCT);

View file

@ -34,6 +34,7 @@ import haveno.core.api.CoreNotificationService;
import haveno.core.api.CorePaymentAccountsService;
import haveno.core.api.XmrConnectionService;
import haveno.core.app.HavenoSetup;
import haveno.core.locale.CurrencyUtil;
import haveno.core.offer.OfferPayload;
import haveno.core.offer.OpenOfferManager;
import haveno.core.support.dispute.arbitration.ArbitrationManager;
@ -94,10 +95,13 @@ public class HavenoUtils {
// configure fees
public static final boolean ARBITRATOR_ASSIGNS_TRADE_FEE_ADDRESS = true;
public static final double PENALTY_FEE_PCT = 0.25; // percent of security deposit to charge for penalty
public static final double MAKER_FEE_PCT = 0.0015; // 0.15%
public static final double TAKER_FEE_PCT = 0.0075; // 0.75%
public static final double MAKER_FEE_FOR_TAKER_WITHOUT_DEPOSIT_PCT = MAKER_FEE_PCT + TAKER_FEE_PCT; // customize maker's fee when no deposit or fee from taker
public static final double PENALTY_FEE_PCT = 0.25; // charge 25% of security deposit for penalty
private static final double MAKER_FEE_PCT_CRYPTO = 0.0015;
private static final double TAKER_FEE_PCT_CRYPTO = 0.0075;
private static final double MAKER_FEE_PCT_TRADITIONAL = 0.0015;
private static final double TAKER_FEE_PCT_TRADITIONAL = 0.0075;
private static final double MAKER_FEE_FOR_TAKER_WITHOUT_DEPOSIT_PCT_CRYPTO = MAKER_FEE_PCT_CRYPTO + TAKER_FEE_PCT_CRYPTO; // can customize maker's fee when no deposit from taker
private static final double MAKER_FEE_FOR_TAKER_WITHOUT_DEPOSIT_PCT_TRADITIONAL = MAKER_FEE_PCT_TRADITIONAL + TAKER_FEE_PCT_TRADITIONAL;
public static final double MINER_FEE_TOLERANCE_FACTOR = 5.0; // miner fees must be within 5x of each other
// other configuration
@ -178,6 +182,26 @@ public class HavenoUtils {
GenUtils.waitFor(waitMs);
}
public static double getMakerFeePct(String currencyCode, boolean hasBuyerAsTakerWithoutDeposit) {
if (CurrencyUtil.isCryptoCurrency(currencyCode)) {
return hasBuyerAsTakerWithoutDeposit ? MAKER_FEE_FOR_TAKER_WITHOUT_DEPOSIT_PCT_CRYPTO : MAKER_FEE_PCT_CRYPTO;
} else if (CurrencyUtil.isTraditionalCurrency(currencyCode)) {
return hasBuyerAsTakerWithoutDeposit ? MAKER_FEE_FOR_TAKER_WITHOUT_DEPOSIT_PCT_TRADITIONAL : MAKER_FEE_PCT_TRADITIONAL;
} else {
throw new IllegalArgumentException("Unsupported currency code: " + currencyCode);
}
}
public static double getTakerFeePct(String currencyCode, boolean hasBuyerAsTakerWithoutDeposit) {
if (CurrencyUtil.isCryptoCurrency(currencyCode)) {
return hasBuyerAsTakerWithoutDeposit ? 0d : TAKER_FEE_PCT_CRYPTO;
} else if (CurrencyUtil.isTraditionalCurrency(currencyCode)) {
return hasBuyerAsTakerWithoutDeposit ? 0d : TAKER_FEE_PCT_TRADITIONAL;
} else {
throw new IllegalArgumentException("Unsupported currency code: " + currencyCode);
}
}
// ----------------------- CONVERSION UTILS -------------------------------
public static BigInteger coinToAtomicUnits(Coin coin) {

View file

@ -697,7 +697,7 @@ public abstract class MutableOfferDataModel extends OfferDataModel {
}
public BigInteger getMaxMakerFee() {
return HavenoUtils.multiply(amount.get(), buyerAsTakerWithoutDeposit.get() ? HavenoUtils.MAKER_FEE_FOR_TAKER_WITHOUT_DEPOSIT_PCT : HavenoUtils.MAKER_FEE_PCT);
return HavenoUtils.multiply(amount.get(), HavenoUtils.getMakerFeePct(tradeCurrencyCode.get(), buyerAsTakerWithoutDeposit.get()));
}
boolean canPlaceOffer() {