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 returnAddress return address for reserved funds
* @param tradeFee trade fee * @param tradeFee trade fee
* @param peerAmount amount to give peer * @param sendAmount amount to give peer
* @param securityDeposit security deposit amount * @param securityDeposit security deposit amount
* @return a transaction to reserve a trade * @return a transaction to reserve a trade
*/ */
public MoneroTxWallet createReserveTx(BigInteger tradeFee, BigInteger peerAmount, BigInteger securityDeposit, String returnAddress) { public MoneroTxWallet createReserveTx(BigInteger tradeFee, BigInteger sendAmount, BigInteger securityDeposit, String returnAddress) {
log.info("Creating reserve tx with fee={}, peerAmount={}, securityDeposit={}", tradeFee, peerAmount, securityDeposit); log.info("Creating reserve tx with fee={}, sendAmount={}, securityDeposit={}", tradeFee, sendAmount, securityDeposit);
return createTradeTx(tradeFee, peerAmount, securityDeposit, returnAddress); return createTradeTx(tradeFee, sendAmount, securityDeposit, returnAddress);
} }
/** /**
@ -324,7 +324,7 @@ public class XmrWalletService {
Offer offer = trade.getProcessModel().getOffer(); Offer offer = trade.getProcessModel().getOffer();
String multisigAddress = trade.getProcessModel().getMultisigAddress(); String multisigAddress = trade.getProcessModel().getMultisigAddress();
BigInteger tradeFee = HavenoUtils.coinToAtomicUnits(trade instanceof MakerTrade ? trade.getOffer().getMakerFee() : trade.getTakerFee()); 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()); BigInteger securityDeposit = HavenoUtils.coinToAtomicUnits(trade instanceof BuyerTrade ? offer.getBuyerSecurityDeposit() : offer.getSellerSecurityDeposit());
// thaw reserved outputs then create deposit tx // thaw reserved outputs then create deposit tx
@ -336,12 +336,12 @@ public class XmrWalletService {
thawOutputs(trade.getSelf().getReserveTxKeyImages()); thawOutputs(trade.getSelf().getReserveTxKeyImages());
} }
log.info("Creating deposit tx with fee={}, peerAmount={}, securityDeposit={}", tradeFee, peerAmount, securityDeposit); log.info("Creating deposit tx with fee={}, sendAmount={}, securityDeposit={}", tradeFee, sendAmount, securityDeposit);
return createTradeTx(tradeFee, peerAmount, securityDeposit, multisigAddress); 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(); MoneroWallet wallet = getWallet();
synchronized (wallet) { synchronized (wallet) {
@ -349,7 +349,7 @@ public class XmrWalletService {
MoneroTxWallet tradeTx = null; MoneroTxWallet tradeTx = null;
double appliedTolerance = 0.0; // percent of tolerance to apply, thereby decreasing security deposit double appliedTolerance = 0.0; // percent of tolerance to apply, thereby decreasing security deposit
double searchDiff = 1.0; // difference for next binary search 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++) { for (int i = 0; i < 10; i++) {
try { try {
BigInteger amount = new BigDecimal(maxAmount).multiply(new BigDecimal(1.0 - SECURITY_DEPOSIT_TOLERANCE * appliedTolerance)).toBigInteger(); 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. * The transaction is submitted to the pool then flushed without relaying.
* *
* @param tradeFee trade fee * @param tradeFee trade fee
* @param peerAmount amount to give peer * @param sendAmount amount to give peer
* @param securityDeposit security deposit amount * @param securityDeposit security deposit amount
* @param address expected destination address for the deposit amount * @param address expected destination address for the deposit amount
* @param txHash transaction hash * @param txHash transaction hash
@ -387,7 +387,7 @@ public class XmrWalletService {
* @param txKey transaction key * @param txKey transaction key
* @param keyImages expected key images of inputs, ignored if null * @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(); MoneroDaemonRpc daemon = getDaemon();
MoneroWallet wallet = getWallet(); MoneroWallet wallet = getWallet();
try { try {
@ -426,7 +426,7 @@ public class XmrWalletService {
// verify deposit amount // verify deposit amount
check = wallet.checkTxKey(txHash, txKey, address); 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");
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()); if (check.getReceivedAmount().compareTo(minAmount) < 0) throw new RuntimeException("Deposit amount is not enough, needed " + minAmount + " but was " + check.getReceivedAmount());
} finally { } finally {
try { try {

View file

@ -27,6 +27,7 @@ import bisq.core.payment.PaymentAccountUtil;
import bisq.core.provider.price.MarketPrice; import bisq.core.provider.price.MarketPrice;
import bisq.core.provider.price.PriceFeedService; import bisq.core.provider.price.PriceFeedService;
import bisq.core.support.dispute.arbitration.arbitrator.ArbitratorManager; import bisq.core.support.dispute.arbitration.arbitrator.ArbitratorManager;
import bisq.core.trade.HavenoUtils;
import bisq.core.trade.statistics.TradeStatisticsManager; 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;
@ -155,7 +156,7 @@ public class CreateOfferService {
String bankId = PaymentAccountUtil.getBankId(paymentAccount); String bankId = PaymentAccountUtil.getBankId(paymentAccount);
List<String> acceptedBanks = PaymentAccountUtil.getAcceptedBanks(paymentAccount); List<String> acceptedBanks = PaymentAccountUtil.getAcceptedBanks(paymentAccount);
double sellerSecurityDeposit = getSellerSecurityDepositAsDouble(buyerSecurityDepositAsDouble); double sellerSecurityDeposit = getSellerSecurityDepositAsDouble(buyerSecurityDepositAsDouble);
Coin makerFeeAsCoin = offerUtil.getMakerFee(amount); Coin makerFeeAsCoin = HavenoUtils.getMakerFee(amount);
Coin buyerSecurityDepositAsCoin = getBuyerSecurityDeposit(amount, buyerSecurityDepositAsDouble); Coin buyerSecurityDepositAsCoin = getBuyerSecurityDeposit(amount, buyerSecurityDepositAsDouble);
Coin sellerSecurityDepositAsCoin = getSellerSecurityDeposit(amount, sellerSecurityDeposit); Coin sellerSecurityDepositAsCoin = getSellerSecurityDeposit(amount, sellerSecurityDeposit);
long maxTradeLimit = offerUtil.getMaxTradeLimit(paymentAccount, currencyCode, direction); 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.payment.PaymentAccount;
import bisq.core.provider.price.MarketPrice; import bisq.core.provider.price.MarketPrice;
import bisq.core.provider.price.PriceFeedService; import bisq.core.provider.price.PriceFeedService;
import bisq.core.trade.HavenoUtils;
import bisq.core.trade.statistics.ReferralIdService; import bisq.core.trade.statistics.ReferralIdService;
import bisq.core.user.AutoConfirmSettings; import bisq.core.user.AutoConfirmSettings;
import bisq.core.user.Preferences; import bisq.core.user.Preferences;
import bisq.core.util.coin.CoinFormatter; import bisq.core.util.coin.CoinFormatter;
import bisq.core.util.coin.CoinUtil;
import bisq.network.p2p.P2PService; import bisq.network.p2p.P2PService;
@ -58,8 +56,6 @@ import java.util.UUID;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import javax.annotation.Nullable;
import static bisq.common.util.MathUtils.roundDoubleToLong; import static bisq.common.util.MathUtils.roundDoubleToLong;
import static bisq.common.util.MathUtils.scaleUpByPowerOf10; import static bisq.common.util.MathUtils.scaleUpByPowerOf10;
import static bisq.core.btc.wallet.Restrictions.getMaxBuyerSecurityDepositAsPercent; import static bisq.core.btc.wallet.Restrictions.getMaxBuyerSecurityDepositAsPercent;
@ -163,41 +159,6 @@ public class OfferUtil {
return MathUtils.roundDouble(manualPrice / marketPrice, 4); 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) { public boolean isBlockChainPaymentMethod(Offer offer) {
return offer != null && offer.getPaymentMethod().isBlockchain(); return offer != null && offer.getPaymentMethod().isBlockchain();
} }

View file

@ -857,14 +857,22 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
return; return;
} }
// verify maker's reserve tx (double spend, trade fee, trade amount, mining fee) // verify maker's trade fee
Offer offer = new Offer(request.getOfferPayload()); Offer offer = new Offer(request.getOfferPayload());
BigInteger tradeFee = HavenoUtils.coinToAtomicUnits(offer.getMakerFee()); BigInteger tradeFee = HavenoUtils.coinToAtomicUnits(HavenoUtils.getMakerFee(offer.getAmount()));
BigInteger peerAmount = HavenoUtils.coinToAtomicUnits(offer.getDirection() == OfferDirection.BUY ? Coin.ZERO : 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)
BigInteger sendAmount = HavenoUtils.coinToAtomicUnits(offer.getDirection() == OfferDirection.BUY ? Coin.ZERO : offer.getAmount());
BigInteger securityDeposit = HavenoUtils.coinToAtomicUnits(offer.getDirection() == OfferDirection.BUY ? offer.getBuyerSecurityDeposit() : offer.getSellerSecurityDeposit()); BigInteger securityDeposit = HavenoUtils.coinToAtomicUnits(offer.getDirection() == OfferDirection.BUY ? offer.getBuyerSecurityDeposit() : offer.getSellerSecurityDeposit());
xmrWalletService.verifyTradeTx( xmrWalletService.verifyTradeTx(
tradeFee, tradeFee,
peerAmount, sendAmount,
securityDeposit, securityDeposit,
request.getPayoutAddress(), request.getPayoutAddress(),
request.getReserveTxHash(), request.getReserveTxHash(),

View file

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

View file

@ -52,10 +52,10 @@ public class MakerReserveOfferFunds extends Task<PlaceOfferModel> {
// create reserve tx // create reserve tx
BigInteger makerFee = HavenoUtils.coinToAtomicUnits(offer.getMakerFee()); 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()); 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();
MoneroTxWallet reserveTx = model.getXmrWalletService().createReserveTx(makerFee, peerAmount, securityDeposit, returnAddress); MoneroTxWallet reserveTx = model.getXmrWalletService().createReserveTx(makerFee, sendAmount, securityDeposit, returnAddress);
// collect reserved key images // collect reserved key images
List<String> reservedKeyImages = new ArrayList<String>(); 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.PaymentAccount;
import bisq.core.payment.payload.PaymentMethod; import bisq.core.payment.payload.PaymentMethod;
import bisq.core.provider.price.PriceFeedService; import bisq.core.provider.price.PriceFeedService;
import bisq.core.trade.HavenoUtils;
import bisq.common.taskrunner.Model; import bisq.common.taskrunner.Model;
import org.bitcoinj.core.Coin; import org.bitcoinj.core.Coin;
@ -111,7 +111,7 @@ public class TakeOfferModel implements Model {
this.securityDeposit = offer.getDirection() == SELL this.securityDeposit = offer.getDirection() == SELL
? offer.getBuyerSecurityDeposit() ? offer.getBuyerSecurityDeposit()
: offer.getSellerSecurityDeposit(); : offer.getSellerSecurityDeposit();
this.takerFee = offerUtil.getTakerFee(amount); this.takerFee = HavenoUtils.getTakerFee(amount);
calculateVolume(); calculateVolume();
calculateTotalToPay(); calculateTotalToPay();

View file

@ -32,8 +32,6 @@ import java.util.UUID;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import javax.annotation.Nullable;
@Slf4j @Slf4j
public final class BuyerAsMakerTrade extends BuyerTrade implements MakerTrade { 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.trade.messages.PaymentSentMessage;
import bisq.core.util.JsonUtil; import bisq.core.util.JsonUtil;
import bisq.core.util.ParsingUtils; import bisq.core.util.ParsingUtils;
import bisq.core.util.coin.CoinUtil;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import java.math.BigDecimal; import java.math.BigDecimal;
@ -39,6 +40,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 javax.annotation.Nullable;
import org.bitcoinj.core.Coin; import org.bitcoinj.core.Coin;
import org.bitcoinj.utils.MonetaryFormat; import org.bitcoinj.utils.MonetaryFormat;
import com.google.common.base.CaseFormat; import com.google.common.base.CaseFormat;
@ -99,8 +102,27 @@ public class HavenoUtils {
private static final MonetaryFormat xmrCoinFormat = Config.baseCurrencyNetworkParameters().getMonetaryFormat(); 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); return ParsingUtils.parseToCoin("0.001", xmrCoinFormat);
} }
@ -108,7 +130,7 @@ public class HavenoUtils {
return ParsingUtils.parseToCoin("0.00005", xmrCoinFormat); return ParsingUtils.parseToCoin("0.00005", xmrCoinFormat);
} }
public static Coin getTakerFeePerBtc() { private static Coin getTakerFeePerXmr() {
return ParsingUtils.parseToCoin("0.003", xmrCoinFormat); return ParsingUtils.parseToCoin("0.003", xmrCoinFormat);
} }
@ -116,6 +138,14 @@ public class HavenoUtils {
return ParsingUtils.parseToCoin("0.00005", xmrCoinFormat); 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. * 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.trade.statistics.TradeStatisticsManager;
import bisq.core.user.User; import bisq.core.user.User;
import bisq.core.util.Validator; import bisq.core.util.Validator;
import bisq.core.util.coin.CoinUtil;
import bisq.network.p2p.BootstrapListener; import bisq.network.p2p.BootstrapListener;
import bisq.network.p2p.DecryptedDirectMessageListener; import bisq.network.p2p.DecryptedDirectMessageListener;
import bisq.network.p2p.DecryptedMessageWithPubKey; import bisq.network.p2p.DecryptedMessageWithPubKey;
@ -456,9 +455,8 @@ public class TradeManager implements PersistedDataHost, DecryptedDirectMessageLi
return; return;
} }
// compute expected taker fee // get expected taker fee
Coin feePerBtc = CoinUtil.getFeePerBtc(HavenoUtils.getTakerFeePerBtc(), Coin.valueOf(offer.getOfferPayload().getAmount())); Coin takerFee = HavenoUtils.getTakerFee(Coin.valueOf(offer.getOfferPayload().getAmount()));
Coin takerFee = CoinUtil.maxCoin(feePerBtc, HavenoUtils.getMinTakerFee());
// create arbitrator trade // create arbitrator trade
trade = new ArbitratorTrade(offer, trade = new ArbitratorTrade(offer,
@ -522,13 +520,17 @@ public class TradeManager implements PersistedDataHost, DecryptedDirectMessageLi
return; return;
} }
// reserve open offer
openOfferManager.reserveOpenOffer(openOffer); // TODO (woodser): reserve offer if arbitrator? probably. or, arbitrator does not have 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; Trade trade;
if (offer.isBuyOffer()) if (offer.isBuyOffer())
trade = new BuyerAsMakerTrade(offer, trade = new BuyerAsMakerTrade(offer,
Coin.valueOf(offer.getOfferPayload().getAmount()), 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(), offer.getOfferPayload().getPrice(),
xmrWalletService, xmrWalletService,
getNewProcessModel(offer), getNewProcessModel(offer),
@ -539,7 +541,7 @@ public class TradeManager implements PersistedDataHost, DecryptedDirectMessageLi
else else
trade = new SellerAsMakerTrade(offer, trade = new SellerAsMakerTrade(offer,
Coin.valueOf(offer.getOfferPayload().getAmount()), Coin.valueOf(offer.getOfferPayload().getAmount()),
Coin.valueOf(offer.getOfferPayload().getMakerFee()), takerFee,
offer.getOfferPayload().getPrice(), offer.getOfferPayload().getPrice(),
xmrWalletService, xmrWalletService,
getNewProcessModel(offer), getNewProcessModel(offer),

View file

@ -75,22 +75,19 @@ public class ArbitratorProcessDepositRequest extends TradeTask {
// collect expected values // collect expected values
Offer offer = trade.getOffer(); Offer offer = trade.getOffer();
boolean isFromTaker = request.getSenderNodeAddress().equals(trade.getTaker().getNodeAddress()); TradingPeer trader = trade.getTradingPeer(request.getSenderNodeAddress());
boolean isFromBuyer = isFromTaker ? offer.getDirection() == OfferDirection.SELL : offer.getDirection() == OfferDirection.BUY; boolean isFromTaker = trader == trade.getTaker();
BigInteger peerAmount = HavenoUtils.coinToAtomicUnits(isFromBuyer ? Coin.ZERO : offer.getAmount()); 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()); BigInteger securityDeposit = HavenoUtils.coinToAtomicUnits(isFromBuyer ? offer.getBuyerSecurityDeposit() : offer.getSellerSecurityDeposit());
String depositAddress = processModel.getMultisigAddress(); 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 // verify deposit tx
try { try {
trade.getXmrWalletService().verifyTradeTx( trade.getXmrWalletService().verifyTradeTx(
tradeFee, tradeFee,
peerAmount, sendAmount,
securityDeposit, securityDeposit,
depositAddress, depositAddress,
trader.getDepositTxHash(), trader.getDepositTxHash(),

View file

@ -54,14 +54,14 @@ 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 values
BigInteger tradeFee = HavenoUtils.coinToAtomicUnits(isFromTaker ? trade.getTakerFee() : offer.getMakerFee()); BigInteger tradeFee = HavenoUtils.coinToAtomicUnits(isFromTaker ? trade.getTakerFee() : trade.getMakerFee());
BigInteger peerAmount = HavenoUtils.coinToAtomicUnits(isFromBuyer ? Coin.ZERO : offer.getAmount()); BigInteger sendAmount = HavenoUtils.coinToAtomicUnits(isFromBuyer ? Coin.ZERO : offer.getAmount());
BigInteger securityDeposit = HavenoUtils.coinToAtomicUnits(isFromBuyer ? offer.getBuyerSecurityDeposit() : offer.getSellerSecurityDeposit()); BigInteger securityDeposit = HavenoUtils.coinToAtomicUnits(isFromBuyer ? offer.getBuyerSecurityDeposit() : offer.getSellerSecurityDeposit());
try { try {
trade.getXmrWalletService().verifyTradeTx( trade.getXmrWalletService().verifyTradeTx(
tradeFee, tradeFee,
peerAmount, sendAmount,
securityDeposit, securityDeposit,
request.getPayoutAddress(), request.getPayoutAddress(),
request.getReserveTxHash(), request.getReserveTxHash(),

View file

@ -44,10 +44,10 @@ public class TakerReserveTradeFunds extends TradeTask {
// create reserve tx // create reserve tx
BigInteger takerFee = HavenoUtils.coinToAtomicUnits(trade.getTakerFee()); 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()); 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();
MoneroTxWallet reserveTx = model.getXmrWalletService().createReserveTx(takerFee, peerAmount, securityDeposit, returnAddress); MoneroTxWallet reserveTx = model.getXmrWalletService().createReserveTx(takerFee, sendAmount, securityDeposit, returnAddress);
// collect reserved key images // collect reserved key images
List<String> reservedKeyImages = new ArrayList<String>(); 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.btc.wallet.Restrictions;
import bisq.core.monetary.Price; import bisq.core.monetary.Price;
import bisq.core.monetary.Volume; import bisq.core.monetary.Volume;
import bisq.core.trade.HavenoUtils;
import bisq.common.util.MathUtils; import bisq.common.util.MathUtils;
import org.bitcoinj.core.Coin; import org.bitcoinj.core.Coin;
import com.google.common.annotations.VisibleForTesting; import com.google.common.annotations.VisibleForTesting;
import javax.annotation.Nullable;
import static bisq.core.util.VolumeUtil.getAdjustedFiatVolume; import static bisq.core.util.VolumeUtil.getAdjustedFiatVolume;
import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkArgument;
public class CoinUtil { 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) { public static Coin minCoin(Coin a, Coin b) {
return a.compareTo(b) <= 0 ? a : b; return a.compareTo(b) <= 0 ? a : b;
@ -87,23 +76,6 @@ public class CoinUtil {
return Coin.valueOf(Math.round(percent * amountAsDouble)); 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 * Calculate the possibly adjusted amount for {@code amount}, taking into account the
* {@code price} and {@code maxTradeLimit} and {@code factor}. * {@code price} and {@code maxTradeLimit} and {@code factor}.

View file

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

View file

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

View file

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

View file

@ -434,14 +434,7 @@ class TakeOfferDataModel extends OfferDataModel {
@Nullable @Nullable
Coin getTakerFee() { Coin getTakerFee() {
Coin amount = this.amount.get(); return HavenoUtils.getTakerFee(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;
}
} }
public void swapTradeToSavings() { public void swapTradeToSavings() {
@ -523,8 +516,4 @@ class TakeOfferDataModel extends OfferDataModel {
public boolean isUsingHalCashAccount() { public boolean isUsingHalCashAccount() {
return paymentAccount.hasPaymentMethodWithId(HAL_CASH_ID); 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()))); priceCurrencyLabel.textProperty().bind(createStringBinding(() -> CurrencyUtil.getCounterCurrency(model.dataModel.getCurrencyCode())));
priceAsPercentageLabel.prefWidthProperty().bind(priceCurrencyLabel.widthProperty()); priceAsPercentageLabel.prefWidthProperty().bind(priceCurrencyLabel.widthProperty());
nextButton.disableProperty().bind(model.isNextButtonDisabled); nextButton.disableProperty().bind(model.isNextButtonDisabled);
tradeFeeInBtcLabel.textProperty().bind(model.tradeFeeInBtcWithFiat); tradeFeeInBtcLabel.textProperty().bind(model.tradeFeeInXmrWithFiat);
tradeFeeDescriptionLabel.textProperty().bind(model.tradeFeeDescription); tradeFeeDescriptionLabel.textProperty().bind(model.tradeFeeDescription);
tradeFeeInBtcLabel.visibleProperty().bind(model.isTradeFeeVisible); tradeFeeInBtcLabel.visibleProperty().bind(model.isTradeFeeVisible);
tradeFeeDescriptionLabel.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 offerWarning = new SimpleStringProperty();
final StringProperty spinnerInfoText = new SimpleStringProperty(""); final StringProperty spinnerInfoText = new SimpleStringProperty("");
final StringProperty tradeFee = new SimpleStringProperty(); final StringProperty tradeFee = new SimpleStringProperty();
final StringProperty tradeFeeInBtcWithFiat = new SimpleStringProperty(); final StringProperty tradeFeeInXmrWithFiat = new SimpleStringProperty();
final StringProperty tradeFeeDescription = new SimpleStringProperty(); final StringProperty tradeFeeDescription = new SimpleStringProperty();
final BooleanProperty isTradeFeeVisible = new SimpleBooleanProperty(false); final BooleanProperty isTradeFeeVisible = new SimpleBooleanProperty(false);
@ -283,8 +283,8 @@ class TakeOfferViewModel extends ActivatableWithDataModel<TakeOfferDataModel> im
isTradeFeeVisible.setValue(true); isTradeFeeVisible.setValue(true);
tradeFee.set(getFormatterForTakerFee().formatCoin(takerFeeAsCoin)); tradeFee.set(getFormatterForTakerFee().formatCoin(takerFeeAsCoin));
tradeFeeInBtcWithFiat.set(OfferViewModelUtil.getTradeFeeWithFiatEquivalent(offerUtil, tradeFeeInXmrWithFiat.set(OfferViewModelUtil.getTradeFeeWithFiatEquivalent(offerUtil,
dataModel.getTakerFeeInBtc(), dataModel.getTakerFee(),
xmrFormatter)); xmrFormatter));
} }
@ -689,7 +689,7 @@ class TakeOfferViewModel extends ActivatableWithDataModel<TakeOfferDataModel> im
public String getTradeFee() { public String getTradeFee() {
return OfferViewModelUtil.getTradeFeeWithFiatEquivalentAndPercentage(offerUtil, return OfferViewModelUtil.getTradeFeeWithFiatEquivalentAndPercentage(offerUtil,
dataModel.getTakerFeeInBtc(), dataModel.getTakerFee(),
dataModel.getAmount().get(), dataModel.getAmount().get(),
xmrFormatter, xmrFormatter,
HavenoUtils.getMinMakerFee()); HavenoUtils.getMinMakerFee());

View file

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