Use new tx and trade fees. UI improvements, remove trade fee from provider data

This commit is contained in:
Manfred Karrer 2017-02-16 15:43:38 -05:00
parent e31dac1e49
commit 0580e73179
16 changed files with 164 additions and 124 deletions

View file

@ -31,28 +31,8 @@ public class FeePolicy {
// https://estimatefee.appspot.com/
// Average values are 10-100 satoshis/byte in january 2016
// Average values are 60-140 satoshis/byte in february 2017
//
// Our trade transactions have a fixed set of inputs and outputs making the size very predictable
// (as long the user does not do multiple funding transactions)
//
// trade fee tx: 226 bytes // 221 satoshi/byte
// deposit tx: 336 bytes // 148 satoshi/byte
// payout tx: 371 bytes // 134 satoshi/byte
// disputed payout tx: 408 bytes // 122 satoshi/byte
// We set a fixed fee to make the needed amounts in the trade predictable.
// We use 0.0005 BTC (0.5 EUR @ 1000 EUR/BTC) which is for our tx sizes about 120-220 satoshi/byte
// We cannot make that user defined as it need to be the same for both users, so we can only change that in
// software updates
// TODO before Beta we should get a good future proof guess as a change causes incompatible versions
// For non trade transactions (withdrawal) we use the default fee calculation
// To avoid issues with not getting into full blocks, we increase the fee/kb to 30 satoshi/byte
// The user can change that in the preferences
// The BitcoinJ fee calculation use kb so a tx size < 1kb will still pay the fee for a kb tx.
// Our payout tx has about 370 bytes so we get a fee/kb value of about 90 satoshi/byte making it high priority
// Other payout transactions (E.g. arbitrators many collected transactions) will go with 30 satoshi/byte if > 1kb
private static Coin NON_TRADE_FEE_PER_KB = Coin.valueOf(40_000); // 0.0004 BTC about 0.16 EUR @ 400 EUR/BTC
private static Coin NON_TRADE_FEE_PER_KB = Coin.valueOf(150_000);
public static void setNonTradeFeePerKb(Coin nonTradeFeePerKb) {
NON_TRADE_FEE_PER_KB = nonTradeFeePerKb;
@ -62,4 +42,8 @@ public class FeePolicy {
return NON_TRADE_FEE_PER_KB;
}
public static Coin getDefaultSecurityDeposit() {
return Coin.valueOf(3_000_000);
}
}

View file

@ -7,12 +7,8 @@ public class FeeData {
private static final Logger log = LoggerFactory.getLogger(FeeData.class);
public final long txFeePerByte;
public final long createOfferFee;
public final long takeOfferFee;
public FeeData(long txFeePerByte, long createOfferFee, long takeOfferFee) {
public FeeData(long txFeePerByte) {
this.txFeePerByte = txFeePerByte;
this.createOfferFee = createOfferFee;
this.takeOfferFee = takeOfferFee;
}
}

View file

@ -14,6 +14,7 @@ import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
//TODO use protobuffer instead of json
public class FeeProvider extends HttpClientProvider {
private static final Logger log = LoggerFactory.getLogger(FeeProvider.class);
@ -28,7 +29,7 @@ public class FeeProvider extends HttpClientProvider {
tsMap.put("bitcoinFeesTs", ((Double) linkedTreeMap.get("bitcoinFeesTs")).longValue());
LinkedTreeMap<String, Double> dataMap = (LinkedTreeMap<String, Double>) linkedTreeMap.get("data");
FeeData feeData = new FeeData(dataMap.get("txFee").longValue(), dataMap.get("createOfferFee").longValue(), dataMap.get("takeOfferFee").longValue());
FeeData feeData = new FeeData(dataMap.get("txFee").longValue());
return new Tuple2<>(tsMap, feeData);
}

View file

@ -21,7 +21,6 @@ import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.SettableFuture;
import com.google.inject.Inject;
import io.bitsquare.app.Log;
import io.bitsquare.btc.provider.ProvidersRepository;
import io.bitsquare.common.UserThread;
import io.bitsquare.common.handlers.FaultHandler;
@ -33,6 +32,7 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.annotation.Nullable;
import java.time.Instant;
import java.util.Map;
import static com.google.common.base.Preconditions.checkNotNull;
@ -40,22 +40,28 @@ import static com.google.common.base.Preconditions.checkNotNull;
public class FeeService {
private static final Logger log = LoggerFactory.getLogger(FeeService.class);
public static final long MIN_TX_FEE = 40; // satoshi/byte
public static final long MAX_TX_FEE = 200;
public static final long DEFAULT_TX_FEE = 100;
public static final long MIN_TX_FEE = 60; // satoshi/byte
public static final long MAX_TX_FEE = 300;
public static final long DEFAULT_TX_FEE = 150;
public static final long MIN_CREATE_OFFER_FEE = 10_000;
public static final long MAX_CREATE_OFFER_FEE = 500_000;
public static final long DEFAULT_CREATE_OFFER_FEE = 30_000;
public static final long MIN_CREATE_OFFER_FEE_IN_BTC = 10_000;
public static final long MAX_CREATE_OFFER_FEE_IN_BTC = 500_000;
public static final long DEFAULT_CREATE_OFFER_FEE_IN_BTC = 30_000; // excluded mining fee
public static final long MIN_TAKE_OFFER_FEE = 10_000;
public static final long MAX_TAKE_OFFER_FEE = 1000_000;
public static final long DEFAULT_TAKE_OFFER_FEE = 80_000;
public static final long MIN_TAKE_OFFER_FEE_IN_BTC = 10_000;
public static final long MAX_TAKE_OFFER_FEE_IN_BTC = 1000_000;
public static final long DEFAULT_TAKE_OFFER_FEE_IN_BTC = 30_000; // excluded mining fee
// 0.00216 btc is for 3 x tx fee for taker -> about 2 EUR!
public static final long MIN_PAUSE_BETWEEN_REQUESTS_IN_MIN = 10;
private final FeeProvider feeProvider;
@Nullable
private FeeData feeData;
private Map<String, Long> timeStampMap;
private long epochInSecondAtLastRequest;
private long lastRequest;
///////////////////////////////////////////////////////////////////////////////////////////
// Constructor
@ -65,7 +71,6 @@ public class FeeService {
public FeeService(HttpClient httpClient,
ProvidersRepository providersRepository) {
this.feeProvider = new FeeProvider(httpClient, providersRepository.getBaseUrl());
feeData = new FeeData(DEFAULT_TX_FEE, DEFAULT_CREATE_OFFER_FEE, DEFAULT_TAKE_OFFER_FEE);
}
public void onAllServicesInitialized() {
@ -73,30 +78,39 @@ public class FeeService {
}
public void requestFees(@Nullable Runnable resultHandler, @Nullable FaultHandler faultHandler) {
//TODO add throttle
Log.traceCall();
FeeRequest feeRequest = new FeeRequest();
SettableFuture<Tuple2<Map<String, Long>, FeeData>> future = feeRequest.getFees(feeProvider);
Futures.addCallback(future, new FutureCallback<Tuple2<Map<String, Long>, FeeData>>() {
@Override
public void onSuccess(@Nullable Tuple2<Map<String, Long>, FeeData> result) {
UserThread.execute(() -> {
checkNotNull(result, "Result must not be null at getFees");
timeStampMap = result.first;
epochInSecondAtLastRequest = timeStampMap.get("bitcoinFeesTs");
feeData = result.second;
if (resultHandler != null)
resultHandler.run();
});
}
long now = Instant.now().getEpochSecond();
if (feeData == null || now - lastRequest > MIN_PAUSE_BETWEEN_REQUESTS_IN_MIN * 60) {
lastRequest = now;
FeeRequest feeRequest = new FeeRequest();
SettableFuture<Tuple2<Map<String, Long>, FeeData>> future = feeRequest.getFees(feeProvider);
Futures.addCallback(future, new FutureCallback<Tuple2<Map<String, Long>, FeeData>>() {
@Override
public void onSuccess(@Nullable Tuple2<Map<String, Long>, FeeData> result) {
UserThread.execute(() -> {
checkNotNull(result, "Result must not be null at getFees");
timeStampMap = result.first;
epochInSecondAtLastRequest = timeStampMap.get("bitcoinFeesTs");
feeData = result.second;
log.info("Tx fee: txFeePerByte=" + feeData.txFeePerByte);
if (resultHandler != null)
resultHandler.run();
});
}
@Override
public void onFailure(@NotNull Throwable throwable) {
log.warn("Could not load fees. " + throwable.toString());
if (faultHandler != null)
UserThread.execute(() -> faultHandler.handleFault("Could not load fees", throwable));
}
});
@Override
public void onFailure(@NotNull Throwable throwable) {
log.warn("Could not load fees. " + throwable.toString());
if (faultHandler != null)
UserThread.execute(() -> faultHandler.handleFault("Could not load fees", throwable));
}
});
} else {
log.debug("We got a requestFees called again before min pause of {} minutes has passed.", MIN_PAUSE_BETWEEN_REQUESTS_IN_MIN);
UserThread.execute(() -> {
if (resultHandler != null)
resultHandler.run();
});
}
}
public Coin getTxFee(int sizeInBytes) {
@ -104,16 +118,20 @@ public class FeeService {
}
public Coin getTxFeePerByte() {
log.info("txFeePerByte = " + feeData.txFeePerByte);
return Coin.valueOf(feeData.txFeePerByte);
if (feeData != null)
return Coin.valueOf(feeData.txFeePerByte);
else
return Coin.valueOf(DEFAULT_TX_FEE);
}
public Coin getCreateOfferFee() {
return Coin.valueOf(feeData.createOfferFee);
// TODO we will get that from the DAO voting
public Coin getCreateOfferFeeInBtc() {
return Coin.valueOf(DEFAULT_CREATE_OFFER_FEE_IN_BTC);
}
public Coin getTakeOfferFee() {
return Coin.valueOf(feeData.takeOfferFee);
// TODO we will get that from the DAO voting
public Coin getTakeOfferFeeInBtc() {
return Coin.valueOf(DEFAULT_TAKE_OFFER_FEE_IN_BTC);
}
public Coin getCreateCompensationRequestFee() {

View file

@ -73,18 +73,18 @@ abstract public class SquBlockchainService {
String genesisTxId)
throws SquBlockchainException {
try {
log.info("blockCount=" + chainHeadHeight);
//log.info("blockCount=" + chainHeadHeight);
long startTs = System.currentTimeMillis();
for (int blockHeight = genesisBlockHeight; blockHeight <= chainHeadHeight; blockHeight++) {
Block block = requestBlock(blockHeight);
log.info("blockHeight=" + blockHeight);
//log.info("blockHeight=" + blockHeight);
parseBlock(new SquBlock(block.getTx(), block.getHeight()),
genesisBlockHeight,
genesisTxId,
utxoByTxIdMap);
}
printUtxoMap(utxoByTxIdMap);
log.info("Took {} ms", System.currentTimeMillis() - startTs);
// log.info("Took {} ms", System.currentTimeMillis() - startTs);
} catch (BitcoindException | CommunicationException e) {
throw new SquBlockchainException(e.getMessage(), e);
}
@ -157,7 +157,7 @@ abstract public class SquBlockchainService {
for (SquTransaction transaction : connectedTxs) {
verifyTransaction(transaction, blockHeight, utxoByTxIdMap);
}
log.info("orphanTxs " + orphanTxs);
//log.info("orphanTxs " + orphanTxs);
if (!orphanTxs.isEmpty() && recursions < maxRecursions)
resolveConnectedTxs(orphanTxs, utxoByTxIdMap, blockHeight, ++recursions, maxRecursions);
}
@ -286,6 +286,6 @@ abstract public class SquBlockchainService {
.append(a.getValue().toString()).append("}\n");
});
});
log.info(sb.toString());
//log.info(sb.toString());
}
}

View file

@ -22,6 +22,7 @@ import io.bitsquare.app.DevFlags;
import io.bitsquare.app.Version;
import io.bitsquare.arbitration.Arbitrator;
import io.bitsquare.btc.AddressEntry;
import io.bitsquare.btc.FeePolicy;
import io.bitsquare.btc.listeners.BalanceListener;
import io.bitsquare.btc.provider.fee.FeeService;
import io.bitsquare.btc.provider.price.PriceFeedService;
@ -143,8 +144,8 @@ class CreateOfferDataModel extends ActivatableDataModel {
useMarketBasedPrice.set(preferences.getUsePercentageBasedPrice());
// TODO add ui for editing
securityDepositAsCoin = Coin.valueOf(1_000_000);
// TODO add ui for editing, use preferences
securityDepositAsCoin = FeePolicy.getDefaultSecurityDeposit();
balanceListener = new BalanceListener(getAddressEntry().getAddress()) {
@Override
@ -255,14 +256,14 @@ class CreateOfferDataModel extends ActivatableDataModel {
// not too many inputs.
// trade fee tx: 226 bytes (1 input) - 374 bytes (2 inputs)
feeService.requestFees(() -> {
createOfferFeeAsCoin = feeService.getCreateOfferFee();
txFeeAsCoin = feeService.getTxFee(400);
calculateTotalToPay();
}, null);
createOfferFeeAsCoin = feeService.getCreateOfferFee();
// Set the default values (in rare cases if the fee request was not done yet we get the hard coded default values)
// But offer creation happens usually after that so we should have already the value from the estimation service.
txFeeAsCoin = feeService.getTxFee(400);
createOfferFeeAsCoin = feeService.getCreateOfferFeeInBtc();
// We request to get the actual estimated fee
requestTxFee();
calculateVolume();
calculateTotalToPay();
@ -419,6 +420,13 @@ class CreateOfferDataModel extends ActivatableDataModel {
this.marketPriceMargin = marketPriceMargin;
}
void requestTxFee() {
feeService.requestFees(() -> {
txFeeAsCoin = feeService.getTxFee(400);
createOfferFeeAsCoin = feeService.getCreateOfferFeeInBtc();
calculateTotalToPay();
}, null);
}
///////////////////////////////////////////////////////////////////////////////////////////
// Getters

View file

@ -334,7 +334,7 @@ public class CreateOfferView extends ActivatableViewAndModel<AnchorPane, CreateO
.show();
key = "createOfferFundWalletInfo";
String tradeAmountText = model.isSellOffer() ? "- Trade amount: " + model.tradeAmount.get() + "\n" : "";
String tradeAmountText = model.isSellOffer() ? "- Trade amount: " + model.getTradeAmount() + "\n" : "";
new Popup().headLine("Fund your offer").instruction("You need to deposit " +
model.totalToPay.get() + " to this offer.\n\n" +
@ -345,7 +345,7 @@ public class CreateOfferView extends ActivatableViewAndModel<AnchorPane, CreateO
"The amount is the sum of:\n" +
tradeAmountText +
"- Security deposit: " + model.getSecurityDeposit() + "\n" +
"- Trading fee: " + model.getOfferFee() + "\n" +
"- Trading fee: " + model.getCreateOfferFee() + "\n" +
"- Mining fee: " + model.getTxFee() + "\n\n" +
"You can choose between two options when funding your trade:\n" +
@ -1078,7 +1078,7 @@ public class CreateOfferView extends ActivatableViewAndModel<AnchorPane, CreateO
addPayInfoEntry(infoGridPane, i++, BSResources.get("createOffer.fundsBox.tradeAmount"), model.tradeAmount.get());
addPayInfoEntry(infoGridPane, i++, BSResources.get("createOffer.fundsBox.securityDeposit"), model.getSecurityDeposit());
addPayInfoEntry(infoGridPane, i++, BSResources.get("createOffer.fundsBox.offerFee"), model.getOfferFee());
addPayInfoEntry(infoGridPane, i++, BSResources.get("createOffer.fundsBox.offerFee"), model.getCreateOfferFee());
addPayInfoEntry(infoGridPane, i++, BSResources.get("createOffer.fundsBox.networkFee"), model.getTxFee());
Separator separator = new Separator();
separator.setOrientation(Orientation.HORIZONTAL);
@ -1099,6 +1099,7 @@ public class CreateOfferView extends ActivatableViewAndModel<AnchorPane, CreateO
private void addPayInfoEntry(GridPane infoGridPane, int row, String labelText, String value) {
Label label = new Label(labelText);
TextField textField = new TextField(value);
textField.setMinWidth(300);
textField.setEditable(false);
textField.setFocusTraversable(false);
textField.setId("payment-info");

View file

@ -36,6 +36,7 @@ import io.bitsquare.gui.main.overlays.popups.Popup;
import io.bitsquare.gui.main.settings.SettingsView;
import io.bitsquare.gui.main.settings.preferences.PreferencesView;
import io.bitsquare.gui.util.BSFormatter;
import io.bitsquare.gui.util.GUIUtil;
import io.bitsquare.gui.util.validation.BtcValidator;
import io.bitsquare.gui.util.validation.FiatValidator;
import io.bitsquare.gui.util.validation.InputValidator;
@ -495,6 +496,7 @@ class CreateOfferViewModel extends ActivatableWithDataModel<CreateOfferDataModel
}
void onShowPayFundsScreen() {
dataModel.requestTxFee();
showPayFundsScreenDisplayed.set(true);
updateSpinnerInfo();
}
@ -659,16 +661,23 @@ class CreateOfferViewModel extends ActivatableWithDataModel<CreateOfferDataModel
return dataModel.getTradeCurrency();
}
public String getOfferFee() {
return formatter.formatCoinWithCode(dataModel.getCreateOfferFeeAsCoin());
}
public String getTxFee() {
return formatter.formatCoinWithCode(dataModel.getTxFeeAsCoin());
public String getTradeAmount() {
return formatter.formatCoinWithCode(dataModel.amount.get());
}
public String getSecurityDeposit() {
return formatter.formatCoinWithCode(dataModel.getSecurityDepositAsCoin());
return formatter.formatCoinWithCode(dataModel.getSecurityDepositAsCoin()) +
GUIUtil.getPercentageOfTradeAmount(dataModel.getSecurityDepositAsCoin(), dataModel.amount.get(), formatter);
}
public String getCreateOfferFee() {
return formatter.formatCoinWithCode(dataModel.getCreateOfferFeeAsCoin()) +
GUIUtil.getPercentageOfTradeAmount(dataModel.getCreateOfferFeeAsCoin(), dataModel.amount.get(), formatter);
}
public String getTxFee() {
return formatter.formatCoinWithCode(dataModel.getTxFeeAsCoin()) +
GUIUtil.getPercentageOfTradeAmount(dataModel.getTxFeeAsCoin(), dataModel.amount.get(), formatter);
}
public PaymentAccount getPaymentAccount() {

View file

@ -174,7 +174,7 @@ class TakeOfferDataModel extends ActivatableDataModel {
// Taker pays 2 times the tx fee because the mining fee might be different when offerer created the offer
// and reserved his funds, so that would not work well with dynamic fees.
// The mining fee for the takeOfferFee tx is deducted from the createOfferFee and not visible to the trader
// The mining fee for the takeOfferFee tx is deducted from the takeOfferFee and not visible to the trader
// The taker pays the mining fee for the trade fee tx and the trade txs.
// A typical trade fee tx has about 226 bytes (if one input). The trade txs has about 336-414 bytes.
@ -190,19 +190,17 @@ class TakeOfferDataModel extends ActivatableDataModel {
// trade fee tx: 226 bytes (1 input) - 374 bytes (2 inputs)
// deposit tx: 336 bytes (1 MS output+ OP_RETURN) - 414 bytes (1 MS output + OP_RETURN + change in case of smaller trade amount)
// payout tx: 371 bytes
// disputed payout tx: 408 bytes
feeService.requestFees(() -> {
//TODO update doubleTxFeeAsCoin and txFeeAsCoin in view with binding
takerFeeAsCoin = feeService.getTakeOfferFee();
txFeeAsCoin = feeService.getTxFee(400);
totalTxFeeAsCoin = txFeeAsCoin.multiply(3);
calculateTotalToPay();
}, null);
// disputed payout tx: 408 bytes
takerFeeAsCoin = feeService.getTakeOfferFee();
// Set the default values (in rare cases if the fee request was not done yet we get the hard coded default values)
// But the "take offer" happens usually after that so we should have already the value from the estimation service.
takerFeeAsCoin = feeService.getTakeOfferFeeInBtc();
txFeeAsCoin = feeService.getTxFee(400);
totalTxFeeAsCoin = txFeeAsCoin.multiply(3);
// We request to get the actual estimated fee
requestTxFee();
calculateVolume();
calculateTotalToPay();
@ -244,6 +242,15 @@ class TakeOfferDataModel extends ActivatableDataModel {
priceFeedService.setCurrencyCode(offer.getCurrencyCode());
}
void requestTxFee() {
feeService.requestFees(() -> {
takerFeeAsCoin = feeService.getTakeOfferFeeInBtc();
txFeeAsCoin = feeService.getTxFee(400);
totalTxFeeAsCoin = txFeeAsCoin.multiply(3);
calculateTotalToPay();
}, null);
}
void onTabSelected(boolean isSelected) {
this.isTabSelected = isSelected;
if (!preferences.getUseStickyMarketPrice() && isTabSelected)
@ -258,11 +265,12 @@ class TakeOfferDataModel extends ActivatableDataModel {
// errorMessageHandler is used only in the check availability phase. As soon we have a trade we write the error msg in the trade object as we want to
// have it persisted as well.
void onTakeOffer(TradeResultHandler tradeResultHandler) {
Coin fundsNeededForTrade = totalToPayAsCoin.get().subtract(takerFeeAsCoin).subtract(txFeeAsCoin);
tradeManager.onTakeOffer(amountAsCoin.get(),
txFeeAsCoin,
takerFeeAsCoin,
tradePrice.getValue(),
totalToPayAsCoin.get().subtract(takerFeeAsCoin).subtract(txFeeAsCoin),
fundsNeededForTrade,
offer,
paymentAccount.getId(),
useSavingsWallet,
@ -460,6 +468,10 @@ class TakeOfferDataModel extends ActivatableDataModel {
return totalTxFeeAsCoin;
}
public Coin getTxFeeAsCoin() {
return txFeeAsCoin;
}
public AddressEntry getAddressEntry() {
return addressEntry;
}

View file

@ -340,7 +340,7 @@ public class TakeOfferView extends ActivatableViewAndModel<AnchorPane, TakeOffer
.show();
key = "takeOfferFundWalletInfo";
String tradeAmountText = model.isSeller() ? "- Trade amount: " + model.getAmount() + "\n" : "";
String tradeAmountText = model.isSeller() ? "- Trade amount: " + model.getTradeAmount() + "\n" : "";
new Popup().headLine("Fund your trade").instruction("You need to deposit " +
model.totalToPay.get() + " for taking this offer.\n\n" +
@ -348,7 +348,7 @@ public class TakeOfferView extends ActivatableViewAndModel<AnchorPane, TakeOffer
tradeAmountText +
"- Security deposit: " + model.getSecurityDeposit() + "\n" +
"- Trading fee: " + model.getTakerFee() + "\n" +
"- Bitcoin mining fee: " + model.getNetworkFee() + "\n\n" +
"- Mining fee (3x): " + model.getTxFee() + "\n\n" +
"You can choose between two options when funding your trade:\n" +
"- Use your Bitsquare wallet (convenient, but transactions may be linkable) OR\n" +
@ -977,11 +977,11 @@ public class TakeOfferView extends ActivatableViewAndModel<AnchorPane, TakeOffer
int i = 0;
if (model.isSeller())
addPayInfoEntry(infoGridPane, i++, BSResources.get("takeOffer.fundsBox.tradeAmount"), model.getAmount());
addPayInfoEntry(infoGridPane, i++, BSResources.get("takeOffer.fundsBox.tradeAmount"), model.getTradeAmount());
addPayInfoEntry(infoGridPane, i++, BSResources.get("takeOffer.fundsBox.securityDeposit"), model.getSecurityDeposit());
addPayInfoEntry(infoGridPane, i++, BSResources.get("takeOffer.fundsBox.offerFee"), model.getTakerFee());
addPayInfoEntry(infoGridPane, i++, BSResources.get("takeOffer.fundsBox.networkFee"), model.getNetworkFee());
addPayInfoEntry(infoGridPane, i++, BSResources.get("takeOffer.fundsBox.networkFee"), model.getTxFee());
Separator separator = new Separator();
separator.setOrientation(Orientation.HORIZONTAL);
separator.setStyle("-fx-background: #666;");
@ -1002,6 +1002,7 @@ public class TakeOfferView extends ActivatableViewAndModel<AnchorPane, TakeOffer
private void addPayInfoEntry(GridPane infoGridPane, int row, String labelText, String value) {
Label label = new Label(labelText);
TextField textField = new TextField(value);
textField.setMinWidth(300);
textField.setEditable(false);
textField.setFocusTraversable(false);
textField.setId("payment-info");

View file

@ -27,6 +27,7 @@ import io.bitsquare.gui.main.funds.FundsView;
import io.bitsquare.gui.main.funds.deposit.DepositView;
import io.bitsquare.gui.main.overlays.popups.Popup;
import io.bitsquare.gui.util.BSFormatter;
import io.bitsquare.gui.util.GUIUtil;
import io.bitsquare.gui.util.validation.BtcValidator;
import io.bitsquare.gui.util.validation.InputValidator;
import io.bitsquare.locale.BSResources;
@ -208,6 +209,7 @@ class TakeOfferViewModel extends ActivatableWithDataModel<TakeOfferDataModel> im
}
public void onShowPayFundsScreen() {
dataModel.requestTxFee();
showPayFundsScreenDisplayed.set(true);
updateSpinnerInfo();
}
@ -569,22 +571,26 @@ class TakeOfferViewModel extends ActivatableWithDataModel<TakeOfferDataModel> im
return amountDescription;
}
String getAmount() {
String getTradeAmount() {
return formatter.formatCoinWithCode(dataModel.amountAsCoin.get());
}
String getTakerFee() {
return formatter.formatCoinWithCode(dataModel.getTakerFeeAsCoin());
}
String getNetworkFee() {
return formatter.formatCoinWithCode(dataModel.getTotalTxFeeAsCoin());
}
public String getSecurityDeposit() {
return formatter.formatCoinWithCode(dataModel.getSecurityDepositAsCoin());
return formatter.formatCoinWithCode(dataModel.getSecurityDepositAsCoin()) +
GUIUtil.getPercentageOfTradeAmount(dataModel.getSecurityDepositAsCoin(), dataModel.amountAsCoin.get(), formatter);
}
public String getTakerFee() {
return formatter.formatCoinWithCode(dataModel.getTakerFeeAsCoin()) +
GUIUtil.getPercentageOfTradeAmount(dataModel.getTakerFeeAsCoin(), dataModel.amountAsCoin.get(), formatter);
}
public String getTxFee() {
return formatter.formatCoinWithCode(dataModel.getTotalTxFeeAsCoin()) +
GUIUtil.getPercentageOfTradeAmount(dataModel.getTotalTxFeeAsCoin(), dataModel.amountAsCoin.get(), formatter);
}
public PaymentMethod getPaymentMethod() {
return dataModel.getPaymentMethod();
}

View file

@ -18,7 +18,6 @@
package io.bitsquare.gui.main.overlays.windows;
import io.bitsquare.arbitration.DisputeManager;
import io.bitsquare.btc.FeePolicy;
import io.bitsquare.gui.components.TextFieldWithCopyIcon;
import io.bitsquare.gui.main.MainView;
import io.bitsquare.gui.main.overlays.Overlay;
@ -131,7 +130,7 @@ public class TradeDetailsWindow extends Overlay<TradeDetailsWindow> {
addLabelTextField(gridPane, ++rowIndex, "Payment method:", BSResources.get(offer.getPaymentMethod().getId()));
// second group
rows = 5;
rows = 6;
PaymentAccountContractData buyerPaymentAccountContractData = null;
PaymentAccountContractData sellerPaymentAccountContractData = null;
@ -173,6 +172,7 @@ public class TradeDetailsWindow extends Overlay<TradeDetailsWindow> {
addLabelTextFieldWithCopyIcon(gridPane, rowIndex, "Trade ID:", trade.getId(), Layout.FIRST_ROW_AND_GROUP_DISTANCE);
addLabelTextField(gridPane, ++rowIndex, "Trade date:", formatter.formatDateTime(trade.getDate()));
addLabelTextField(gridPane, ++rowIndex, "Security deposit:", formatter.formatCoinWithCode(offer.getSecurityDeposit()));
addLabelTextField(gridPane, ++rowIndex, "Tx fee:", formatter.formatCoinWithCode(trade.getTxFee()));
addLabelTextFieldWithCopyIcon(gridPane, ++rowIndex, "Selected arbitrator:", trade.getArbitratorNodeAddress().getFullAddress());
if (trade.getTradingPeerNodeAddress() != null)

View file

@ -39,6 +39,7 @@ import javafx.stage.DirectoryChooser;
import javafx.stage.FileChooser;
import javafx.stage.Stage;
import javafx.util.StringConverter;
import org.bitcoinj.core.Coin;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -300,4 +301,9 @@ public class GUIUtil {
e.printStackTrace();
}
}
public static String getPercentageOfTradeAmount(Coin fee, Coin tradeAmount, BSFormatter formatter) {
return " (" + formatter.formatToPercentWithSymbol((double) fee.value / (double) tradeAmount.value) +
" of trade amount)";
}
}

View file

@ -180,7 +180,7 @@ takeOffer.fundsBox.sell.info=For every offer there is a dedicated trade wallet.
takeOffer.fundsBox.tradeAmount=Amount to sell:
takeOffer.fundsBox.securityDeposit=Security deposit:
takeOffer.fundsBox.offerFee=Take offer fee:
takeOffer.fundsBox.networkFee=Mining fee:
takeOffer.fundsBox.networkFee=Mining fees (3x):
takeOffer.fundsBox.total=Total:
takeOffer.fundsBox.showAdvanced=Show advanced settings
takeOffer.fundsBox.hideAdvanced=Hide advanced settings

View file

@ -32,6 +32,7 @@ import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.ConcurrentHashMap;
//TODO use protobuffer instead of json
public class FeeRequestService {
private static final Logger log = LoggerFactory.getLogger(FeeRequestService.class);
@ -47,10 +48,7 @@ public class FeeRequestService {
public FeeRequestService() throws IOException {
btcFeesProvider = new BtcFeesProvider();
allFeesMap.put("txFee", FeeService.DEFAULT_TX_FEE);
allFeesMap.put("createOfferFee", FeeService.DEFAULT_CREATE_OFFER_FEE);
allFeesMap.put("takeOfferFee", FeeService.DEFAULT_TAKE_OFFER_FEE);
writeToJson();
startRequests();
}
@ -101,5 +99,4 @@ public class FeeRequestService {
public String getJson() {
return json;
}
}

View file

@ -12,6 +12,7 @@ import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
//TODO use protobuffer instead of json
public class BtcFeesProvider {
private static final Logger log = LoggerFactory.getLogger(BtcFeesProvider.class);
@ -23,7 +24,7 @@ public class BtcFeesProvider {
public Long getFee() throws IOException, HttpException {
String response = httpClient.requestWithGET("recommended", "User-Agent", "");
log.debug("Get recommended fee response: " + response);
log.info("Get recommended fee response: " + response);
Map<String, Long> map = new HashMap<>();
LinkedTreeMap<String, Double> treeMap = new Gson().fromJson(response, LinkedTreeMap.class);
treeMap.entrySet().stream().forEach(e -> map.put(e.getKey(), e.getValue().longValue()));