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, useMarketBasedPriceValue,
amountAsLong, amountAsLong,
minAmountAsLong, minAmountAsLong,
hasBuyerAsTakerWithoutDeposit ? HavenoUtils.MAKER_FEE_FOR_TAKER_WITHOUT_DEPOSIT_PCT : HavenoUtils.MAKER_FEE_PCT, HavenoUtils.getMakerFeePct(currencyCode, hasBuyerAsTakerWithoutDeposit),
hasBuyerAsTakerWithoutDeposit ? 0d : HavenoUtils.TAKER_FEE_PCT, HavenoUtils.getTakerFeePct(currencyCode, hasBuyerAsTakerWithoutDeposit),
HavenoUtils.PENALTY_FEE_PCT, HavenoUtils.PENALTY_FEE_PCT,
hasBuyerAsTakerWithoutDeposit ? 0d : securityDepositPct, // buyer as taker security deposit is optional for private offers hasBuyerAsTakerWithoutDeposit ? 0d : securityDepositPct, // buyer as taker security deposit is optional for private offers
securityDepositPct, securityDepositPct,

View file

@ -1595,8 +1595,9 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
if (hasBuyerAsTakerWithoutDeposit) { if (hasBuyerAsTakerWithoutDeposit) {
// verify maker's trade fee // verify maker's trade fee
if (offer.getMakerFeePct() != HavenoUtils.MAKER_FEE_FOR_TAKER_WITHOUT_DEPOSIT_PCT) { double makerFeePct = HavenoUtils.getMakerFeePct(request.getOfferPayload().getCounterCurrencyCode(), hasBuyerAsTakerWithoutDeposit);
errorMessage = "Wrong maker fee for offer " + request.offerId + ". Expected " + HavenoUtils.MAKER_FEE_FOR_TAKER_WITHOUT_DEPOSIT_PCT + " but got " + offer.getMakerFeePct(); if (offer.getMakerFeePct() != makerFeePct) {
errorMessage = "Wrong maker fee for offer " + request.offerId + ". Expected " + makerFeePct + " but got " + offer.getMakerFeePct();
log.warn(errorMessage); log.warn(errorMessage);
sendAckMessage(request.getClass(), peer, request.getPubKeyRing(), request.getOfferId(), request.getUid(), false, errorMessage); sendAckMessage(request.getClass(), peer, request.getPubKeyRing(), request.getOfferId(), request.getUid(), false, errorMessage);
return; return;
@ -1636,16 +1637,18 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
} }
// verify maker's trade fee // verify maker's trade fee
if (offer.getMakerFeePct() != HavenoUtils.MAKER_FEE_PCT) { double makerFeePct = HavenoUtils.getMakerFeePct(request.getOfferPayload().getCounterCurrencyCode(), hasBuyerAsTakerWithoutDeposit);
errorMessage = "Wrong maker fee for offer " + request.offerId + ". Expected " + HavenoUtils.MAKER_FEE_PCT + " but got " + offer.getMakerFeePct(); if (offer.getMakerFeePct() != makerFeePct) {
errorMessage = "Wrong maker fee for offer " + request.offerId + ". Expected " + makerFeePct + " but got " + offer.getMakerFeePct();
log.warn(errorMessage); log.warn(errorMessage);
sendAckMessage(request.getClass(), peer, request.getPubKeyRing(), request.getOfferId(), request.getUid(), false, errorMessage); sendAckMessage(request.getClass(), peer, request.getPubKeyRing(), request.getOfferId(), request.getUid(), false, errorMessage);
return; return;
} }
// verify taker's trade fee // verify taker's trade fee
if (offer.getTakerFeePct() != HavenoUtils.TAKER_FEE_PCT) { double takerFeePct = HavenoUtils.getTakerFeePct(request.getOfferPayload().getCounterCurrencyCode(), hasBuyerAsTakerWithoutDeposit);
errorMessage = "Wrong taker fee for offer " + request.offerId + ". Expected " + HavenoUtils.TAKER_FEE_PCT + " but got " + offer.getTakerFeePct(); if (offer.getTakerFeePct() != takerFeePct) {
errorMessage = "Wrong taker fee for offer " + request.offerId + ". Expected " + takerFeePct + " but got " + offer.getTakerFeePct();
log.warn(errorMessage); log.warn(errorMessage);
sendAckMessage(request.getClass(), peer, request.getPubKeyRing(), request.getOfferId(), request.getUid(), false, errorMessage); sendAckMessage(request.getClass(), peer, request.getPubKeyRing(), request.getOfferId(), request.getUid(), false, errorMessage);
return; 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) // 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 sendTradeAmount = offer.getDirection() == OfferDirection.BUY ? BigInteger.ZERO : offer.getAmount();
BigInteger securityDeposit = offer.getDirection() == OfferDirection.BUY ? offer.getMaxBuyerSecurityDeposit() : offer.getMaxSellerSecurityDeposit(); BigInteger securityDeposit = offer.getDirection() == OfferDirection.BUY ? offer.getMaxBuyerSecurityDeposit() : offer.getMaxSellerSecurityDeposit();
BigInteger penaltyFee = HavenoUtils.multiply(securityDeposit, HavenoUtils.PENALTY_FEE_PCT); 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.CorePaymentAccountsService;
import haveno.core.api.XmrConnectionService; import haveno.core.api.XmrConnectionService;
import haveno.core.app.HavenoSetup; import haveno.core.app.HavenoSetup;
import haveno.core.locale.CurrencyUtil;
import haveno.core.offer.OfferPayload; import haveno.core.offer.OfferPayload;
import haveno.core.offer.OpenOfferManager; import haveno.core.offer.OpenOfferManager;
import haveno.core.support.dispute.arbitration.ArbitrationManager; import haveno.core.support.dispute.arbitration.ArbitrationManager;
@ -94,10 +95,13 @@ public class HavenoUtils {
// configure fees // configure fees
public static final boolean ARBITRATOR_ASSIGNS_TRADE_FEE_ADDRESS = true; 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 PENALTY_FEE_PCT = 0.25; // charge 25% of security deposit for penalty
public static final double MAKER_FEE_PCT = 0.0015; // 0.15% private static final double MAKER_FEE_PCT_CRYPTO = 0.0015;
public static final double TAKER_FEE_PCT = 0.0075; // 0.75% private static final double TAKER_FEE_PCT_CRYPTO = 0.0075;
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 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 public static final double MINER_FEE_TOLERANCE_FACTOR = 5.0; // miner fees must be within 5x of each other
// other configuration // other configuration
@ -178,6 +182,26 @@ public class HavenoUtils {
GenUtils.waitFor(waitMs); 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 ------------------------------- // ----------------------- CONVERSION UTILS -------------------------------
public static BigInteger coinToAtomicUnits(Coin coin) { public static BigInteger coinToAtomicUnits(Coin coin) {

View file

@ -697,7 +697,7 @@ public abstract class MutableOfferDataModel extends OfferDataModel {
} }
public BigInteger getMaxMakerFee() { 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() { boolean canPlaceOffer() {