From f723bf57376aad5a3c5ec6742150d726f434feeb Mon Sep 17 00:00:00 2001 From: Manfred Karrer Date: Thu, 21 Jan 2016 02:40:49 +0100 Subject: [PATCH] Refactor fee handling, add user defined fee to settings, UI fixes --- .../main/java/io/bitsquare/btc/FeePolicy.java | 74 +++++++++++++------ .../java/io/bitsquare/btc/Restrictions.java | 8 +- .../io/bitsquare/btc/TradeWalletService.java | 25 ++++--- .../java/io/bitsquare/btc/WalletService.java | 49 ++++++++---- .../bitsquare/trade/BuyerAsOffererTrade.java | 2 +- .../io/bitsquare/trade/BuyerAsTakerTrade.java | 2 +- .../main/java/io/bitsquare/trade/Trade.java | 2 +- .../java/io/bitsquare/trade/TradeManager.java | 7 -- .../tasks/BroadcastCreateOfferFeeTx.java | 2 +- .../placeoffer/tasks/CreateOfferFeeTx.java | 2 +- .../buyer/CreateAndSignDepositTxAsBuyer.java | 4 +- .../buyer/CreateDepositTxInputsAsBuyer.java | 2 +- .../tasks/buyer/SignAndFinalizePayoutTx.java | 2 +- .../CreateAndSignDepositTxAsSeller.java | 4 +- .../seller/CreateDepositTxInputsAsSeller.java | 2 +- .../trade/tasks/seller/SignPayoutTx.java | 2 +- .../tasks/taker/CreateTakeOfferFeeTx.java | 2 +- .../java/io/bitsquare/user/Preferences.java | 22 ++++++ .../io/bitsquare/btc/RestrictionsTest.java | 18 ++--- .../java/io/bitsquare/gui/main/MainView.java | 2 +- .../io/bitsquare/gui/main/MainViewModel.java | 1 - .../disputes/trader/DisputeSummaryPopup.java | 2 +- .../main/funds/reserved/ReservedListItem.java | 11 +-- .../main/funds/withdrawal/WithdrawalView.java | 67 ++++++++--------- .../createoffer/CreateOfferDataModel.java | 6 +- .../offer/createoffer/CreateOfferView.java | 2 +- .../offer/takeoffer/TakeOfferDataModel.java | 17 +++-- .../main/offer/takeoffer/TakeOfferView.java | 2 +- .../gui/main/portfolio/PortfolioView.fxml | 7 +- .../pendingtrades/PendingTradesDataModel.java | 2 +- .../pendingtrades/PendingTradesViewModel.java | 2 +- .../pendingtrades/steps/CompletedView.java | 2 +- .../settings/preferences/PreferencesView.java | 16 +++- .../preferences/PreferencesViewModel.java | 24 +++++- .../gui/popups/EmptyWalletPopup.java | 2 +- .../resources/i18n/displayStrings.properties | 2 +- 36 files changed, 255 insertions(+), 143 deletions(-) diff --git a/core/src/main/java/io/bitsquare/btc/FeePolicy.java b/core/src/main/java/io/bitsquare/btc/FeePolicy.java index 161c88a7b1..3bfb7212cc 100644 --- a/core/src/main/java/io/bitsquare/btc/FeePolicy.java +++ b/core/src/main/java/io/bitsquare/btc/FeePolicy.java @@ -18,39 +18,67 @@ package io.bitsquare.btc; import org.bitcoinj.core.Coin; -import org.bitcoinj.core.Wallet; public class FeePolicy { - // Official min. fee and fee per kiloByte dropped down to 0.00001 BTC / Coin.valueOf(1000) / 1000 satoshis, but as there are reported problems with - // confirmation we use a hgher value. - // The should also help to avoid problems when the tx size is larger as the standard (e.g. if the user does not pay - // in with one transaction but several tx). We don't do a dynamically fee calculation as we need predictable amounts, so that should help to get a larger - // headroom. - // Andreas Schildbach reported problems with confirmation and increased the fee/offered UI side fee setting. - - // http://www.cointape.com/ - // The fastest and cheapest transaction fee is currently 50 satoshis/byte, shown in green at the top. - // For the average transaction size of 597 bytes, this results in a fee of 298 bits (0.298 mBTC). -> 0.0003 BTC or Coin.valueOf(30000); - + // With block getting filled up the needed fee to get fast into a black has become more expensive and less predictable. + // To see current fees check out: + // https://tradeblock.com/blockchain + // http://www.cointape.com + // Average values are 10-100 satoshis/byte in january 2016 + // + // 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 // deposit tx: 336 bytes // payout tx: 371 bytes - // disputed payout tx: 408 bytes -> 20400 satoshis with 50 satoshis/byte + // disputed payout tx: 408 bytes - // Other good source is: https://tradeblock.com/blockchain 15-100 satoshis/byte - - public static final Coin TX_FEE = Coin.valueOf(30000); // 0.0003 BTC about 1.2 EUR @ 400 EUR/BTC: about 100 satoshi /byte - - static { - Wallet.SendRequest.DEFAULT_FEE_PER_KB = TX_FEE; + // We set a fixed fee to make the needed amounts in the trade predictable. + // We use 0.0003 BTC (0.12 EUR @ 400 EUR/BTC) which is for our tx sizes about 75-150 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 + public static Coin getFixedTxFeeForTrades() { + return Coin.valueOf(30_000); } - public static final Coin CREATE_OFFER_FEE = Coin.valueOf(100000); // 0.001 BTC 0.1% of 1 BTC about 0.4 EUR @ 400 EUR/BTC - public static final Coin TAKE_OFFER_FEE = CREATE_OFFER_FEE; + // 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 FEE_PER_KB = Coin.valueOf(30_000); // 0.0003 BTC about 0.12 EUR @ 400 EUR/BTC - // TODO make final again later - public static Coin SECURITY_DEPOSIT = Coin.valueOf(10000000); // 0.1 BTC; about 4 EUR @ 400 EUR/BTC + public static void setFeePerKb(Coin feePerKb) { + FEE_PER_KB = feePerKb; + } + + public static Coin getFeePerKb() { + return FEE_PER_KB; + } + + + // 0.001 BTC 0.1% of 1 BTC about 0.4 EUR @ 400 EUR/BTC + public static Coin getCreateOfferFee() { + return Coin.valueOf(100_000); + } + + // Currently we use the same fee for both offerer and taker + public static Coin getTakeOfferFee() { + return getCreateOfferFee(); + } + + + // TODO make final again later 100_000_000 + // 0.1 BTC; about 4 EUR @ 400 EUR/BTC + private static Coin SECURITY_DEPOSIT = Coin.valueOf(10_000_000); + + public static Coin getSecurityDeposit() { + return SECURITY_DEPOSIT; + } // Called from WalletService to reduce SECURITY_DEPOSIT for mainnet to 0.01 btc // TODO remove later when tested enough diff --git a/core/src/main/java/io/bitsquare/btc/Restrictions.java b/core/src/main/java/io/bitsquare/btc/Restrictions.java index 20983dbc96..9d0128decb 100644 --- a/core/src/main/java/io/bitsquare/btc/Restrictions.java +++ b/core/src/main/java/io/bitsquare/btc/Restrictions.java @@ -34,7 +34,11 @@ public class Restrictions { MAX_TRADE_AMOUNT = maxTradeAmount; } - public static boolean isMinSpendableAmount(Coin amount) { - return amount != null && amount.compareTo(FeePolicy.TX_FEE.add(Transaction.MIN_NONDUST_OUTPUT)) > 0; + public static boolean isAboveFixedTxFeeAndDust(Coin amount) { + return amount != null && amount.compareTo(FeePolicy.getFixedTxFeeForTrades().add(Transaction.MIN_NONDUST_OUTPUT)) > 0; + } + + public static boolean isAboveDust(Coin amount) { + return amount != null && amount.compareTo(Transaction.MIN_NONDUST_OUTPUT) > 0; } } diff --git a/core/src/main/java/io/bitsquare/btc/TradeWalletService.java b/core/src/main/java/io/bitsquare/btc/TradeWalletService.java index 841cedcdc5..d0b2a978a9 100644 --- a/core/src/main/java/io/bitsquare/btc/TradeWalletService.java +++ b/core/src/main/java/io/bitsquare/btc/TradeWalletService.java @@ -142,9 +142,9 @@ public class TradeWalletService { public Transaction createTradingFeeTx(AddressEntry addressEntry, Coin tradingFee, String feeReceiverAddresses) throws InsufficientMoneyException, AddressFormatException { Transaction tradingFeeTx = new Transaction(params); - Preconditions.checkArgument(Restrictions.isMinSpendableAmount(tradingFee), + Preconditions.checkArgument(Restrictions.isAboveFixedTxFeeAndDust(tradingFee), "You cannot send an amount which are smaller than the fee + dust output."); - Coin outPutAmount = tradingFee.subtract(FeePolicy.TX_FEE); + Coin outPutAmount = tradingFee.subtract(FeePolicy.getFixedTxFeeForTrades()); tradingFeeTx.addOutput(outPutAmount, new Address(params, feeReceiverAddresses)); // we allow spending of unconfirmed tx (double spend risk is low and usability would suffer if we need to @@ -154,14 +154,13 @@ public class TradeWalletService { sendRequest.shuffleOutputs = false; sendRequest.aesKey = aesKey; sendRequest.coinSelector = new AddressBasedCoinSelector(params, addressEntry); - + // We use a fixed fee + sendRequest.feePerKb = Coin.ZERO; + sendRequest.fee = FeePolicy.getFixedTxFeeForTrades(); // We use always the same address for all transactions in a trade to keep things simple. // To be discussed if that introduce any privacy issues. sendRequest.changeAddress = addressEntry.getAddress(); - // Wallet.SendRequest.DEFAULT_FEE_PER_KB is set in FeePolicy to our defined amount - // We don't want to risk delayed transactions so we set the fee rather high. - // Delayed tx will lead to a broken chain of the deposit transaction. wallet.completeTx(sendRequest); printTxWithInputs("tradingFeeTx", tradingFeeTx); @@ -212,7 +211,7 @@ public class TradeWalletService { */ // inputAmount includes the tx fee. So we subtract the fee to get the dummyOutputAmount. - Coin dummyOutputAmount = inputAmount.subtract(FeePolicy.TX_FEE); + Coin dummyOutputAmount = inputAmount.subtract(FeePolicy.getFixedTxFeeForTrades()); Transaction dummyTX = new Transaction(params); // The output is just used to get the right inputs and change outputs, so we use an anonymous ECKey, as it will never be used for anything. @@ -303,7 +302,7 @@ public class TradeWalletService { // First we construct a dummy TX to get the inputs and outputs we want to use for the real deposit tx. // Similar to the way we did in the createTakerDepositTxInputs method. Transaction dummyTx = new Transaction(params); - Coin dummyOutputAmount = offererInputAmount.subtract(FeePolicy.TX_FEE); + Coin dummyOutputAmount = offererInputAmount.subtract(FeePolicy.getFixedTxFeeForTrades()); TransactionOutput dummyOutput = new TransactionOutput(params, dummyTx, dummyOutputAmount, new ECKey().toAddress(params)); dummyTx.addOutput(dummyOutput); addAvailableInputsAndChangeOutputs(dummyTx, offererAddressInfo); @@ -640,7 +639,6 @@ public class TradeWalletService { return payoutTx; } - /////////////////////////////////////////////////////////////////////////////////////////// // Dispute /////////////////////////////////////////////////////////////////////////////////////////// @@ -694,12 +692,15 @@ public class TradeWalletService { preparedPayoutTx.addOutput(buyerPayoutAmount, new Address(params, buyerAddressString)); if (sellerPayoutAmount.isGreaterThan(Coin.ZERO)) preparedPayoutTx.addOutput(sellerPayoutAmount, new Address(params, sellerAddressString)); - if (arbitratorPayoutAmount.isGreaterThan(Coin.ZERO)) + if (arbitratorPayoutAmount.isGreaterThan(Coin.ZERO) && arbitratorAddressEntry.getAddressString() != null) preparedPayoutTx.addOutput(arbitratorPayoutAmount, new Address(params, arbitratorAddressEntry.getAddressString())); // take care of sorting! Script redeemScript = getMultiSigRedeemScript(buyerPubKey, sellerPubKey, arbitratorPubKey); Sha256Hash sigHash = preparedPayoutTx.hashForSignature(0, redeemScript, Transaction.SigHash.ALL, false); + if (arbitratorAddressEntry.getKeyPair() == null) + throw new RuntimeException("Unexpected null value: arbitratorAddressEntry.getKeyPair() must not be null"); + ECKey.ECDSASignature arbitratorSignature = arbitratorAddressEntry.getKeyPair().sign(sigHash, aesKey).toCanonicalised(); verifyTransaction(preparedPayoutTx); @@ -1026,6 +1027,9 @@ public class TradeWalletService { Wallet.SendRequest sendRequest = Wallet.SendRequest.forTx(transaction); sendRequest.shuffleOutputs = false; sendRequest.aesKey = aesKey; + // We use a fixed fee + sendRequest.feePerKb = Coin.ZERO; + sendRequest.fee = FeePolicy.getFixedTxFeeForTrades(); // we allow spending of unconfirmed tx (double spend risk is low and usability would suffer if we need to wait for 1 confirmation) sendRequest.coinSelector = new AddressBasedCoinSelector(params, addressEntry); // We use always the same address in a trade for all transactions @@ -1052,4 +1056,5 @@ public class TradeWalletService { } return balance; }*/ + } diff --git a/core/src/main/java/io/bitsquare/btc/WalletService.java b/core/src/main/java/io/bitsquare/btc/WalletService.java index f7b9a2a789..247a90ae4b 100644 --- a/core/src/main/java/io/bitsquare/btc/WalletService.java +++ b/core/src/main/java/io/bitsquare/btc/WalletService.java @@ -438,15 +438,29 @@ public class WalletService { // Withdrawal /////////////////////////////////////////////////////////////////////////////////////////// - public String sendFunds(String fromAddress, - String toAddress, - Coin amount, - KeyParameter aesKey, - FutureCallback callback) throws AddressFormatException, IllegalArgumentException, InsufficientMoneyException { + public Coin getRequiredFee(String fromAddress, + String toAddress, + Coin amount) throws AddressFormatException, IllegalArgumentException, InsufficientMoneyException { + Coin fee; + try { + wallet.completeTx(getSendRequest(fromAddress, toAddress, amount, null)); + fee = Coin.ZERO; + } catch (InsufficientMoneyException e) { + log.info("The amount to be transferred is not enough to pay the transaction fees of {}. " + + "We subtract that fee from the receivers amount to make the transaction possible."); + fee = e.missing; + } + return fee; + } + + public Wallet.SendRequest getSendRequest(String fromAddress, + String toAddress, + Coin amount, + @Nullable KeyParameter aesKey) throws AddressFormatException, IllegalArgumentException, InsufficientMoneyException { Transaction tx = new Transaction(params); - Preconditions.checkArgument(Restrictions.isMinSpendableAmount(amount), - "You cannot send an amount which are smaller than the fee + dust output."); - tx.addOutput(amount.subtract(FeePolicy.TX_FEE), new Address(params, toAddress)); + Preconditions.checkArgument(Restrictions.isAboveDust(amount), + "You cannot send an amount which are smaller than 546 satoshis."); + tx.addOutput(amount, new Address(params, toAddress)); Wallet.SendRequest sendRequest = Wallet.SendRequest.forTx(tx); sendRequest.aesKey = aesKey; @@ -457,13 +471,21 @@ public class WalletService { sendRequest.coinSelector = new AddressBasedCoinSelector(params, addressEntry.get()); sendRequest.changeAddress = addressEntry.get().getAddress(); - Wallet.SendResult sendResult = wallet.sendCoins(sendRequest); + sendRequest.feePerKb = FeePolicy.getFeePerKb(); + return sendRequest; + } + + public String sendFunds(String fromAddress, + String toAddress, + Coin amount, + KeyParameter aesKey, + FutureCallback callback) throws AddressFormatException, IllegalArgumentException, InsufficientMoneyException { + Coin fee = getRequiredFee(fromAddress, toAddress, amount); + Wallet.SendResult sendResult = wallet.sendCoins(getSendRequest(fromAddress, toAddress, amount.subtract(fee), aesKey)); Futures.addCallback(sendResult.broadcastComplete, callback); - printTxWithInputs("sendFunds", tx); - log.debug("tx=" + tx); - - return tx.getHashAsString(); + printTxWithInputs("sendFunds", sendResult.tx); + return sendResult.tx.getHashAsString(); } public void emptyWallet(String toAddress, KeyParameter aesKey, ResultHandler resultHandler, ErrorMessageHandler errorMessageHandler) @@ -471,6 +493,7 @@ public class WalletService { Wallet.SendRequest sendRequest = Wallet.SendRequest.emptyWallet(new Address(params, toAddress)); sendRequest.aesKey = aesKey; Wallet.SendResult sendResult = wallet.sendCoins(sendRequest); + sendRequest.feePerKb = FeePolicy.getFeePerKb(); Futures.addCallback(sendResult.broadcastComplete, new FutureCallback() { @Override public void onSuccess(Transaction result) { diff --git a/core/src/main/java/io/bitsquare/trade/BuyerAsOffererTrade.java b/core/src/main/java/io/bitsquare/trade/BuyerAsOffererTrade.java index faf6cf63e9..9ec7444ab6 100644 --- a/core/src/main/java/io/bitsquare/trade/BuyerAsOffererTrade.java +++ b/core/src/main/java/io/bitsquare/trade/BuyerAsOffererTrade.java @@ -79,7 +79,7 @@ public class BuyerAsOffererTrade extends BuyerTrade implements OffererTrade, Ser public Coin getPayoutAmount() { checkNotNull(getTradeAmount(), "Invalid state: getTradeAmount() = null"); - return FeePolicy.SECURITY_DEPOSIT.add(getTradeAmount()); + return FeePolicy.getSecurityDeposit().add(getTradeAmount()); } } diff --git a/core/src/main/java/io/bitsquare/trade/BuyerAsTakerTrade.java b/core/src/main/java/io/bitsquare/trade/BuyerAsTakerTrade.java index bd90546f9b..7088c2b0b2 100644 --- a/core/src/main/java/io/bitsquare/trade/BuyerAsTakerTrade.java +++ b/core/src/main/java/io/bitsquare/trade/BuyerAsTakerTrade.java @@ -80,6 +80,6 @@ public class BuyerAsTakerTrade extends BuyerTrade implements TakerTrade, Seriali public Coin getPayoutAmount() { checkNotNull(getTradeAmount(), "Invalid state: getTradeAmount() = null"); - return FeePolicy.SECURITY_DEPOSIT.add(getTradeAmount()); + return FeePolicy.getSecurityDeposit().add(getTradeAmount()); } } diff --git a/core/src/main/java/io/bitsquare/trade/Trade.java b/core/src/main/java/io/bitsquare/trade/Trade.java index 6911a7a19c..9ce3a63362 100644 --- a/core/src/main/java/io/bitsquare/trade/Trade.java +++ b/core/src/main/java/io/bitsquare/trade/Trade.java @@ -368,7 +368,7 @@ abstract public class Trade implements Tradable, Model, Serializable { } public Coin getPayoutAmount() { - return FeePolicy.SECURITY_DEPOSIT; + return FeePolicy.getSecurityDeposit(); } public ProcessModel getProcessModel() { diff --git a/core/src/main/java/io/bitsquare/trade/TradeManager.java b/core/src/main/java/io/bitsquare/trade/TradeManager.java index 8cc30f7876..b2c3b162fe 100644 --- a/core/src/main/java/io/bitsquare/trade/TradeManager.java +++ b/core/src/main/java/io/bitsquare/trade/TradeManager.java @@ -131,7 +131,6 @@ public class TradeManager { log.trace("onMailboxMessageAdded senderAddress: " + senderAddress); Message message = decryptedMsgWithPubKey.message; if (message instanceof PayDepositRequest) { - //TODO is that used???? PayDepositRequest payDepositRequest = (PayDepositRequest) message; log.trace("Received payDepositRequest: " + payDepositRequest); if (payDepositRequest.getSenderAddress().equals(senderAddress)) @@ -163,12 +162,6 @@ public class TradeManager { // Lifecycle /////////////////////////////////////////////////////////////////////////////////////////// - // When all services are initialized we create the protocols for our open offers and persisted pendingTrades - // OffererAsBuyerProtocol listens for take offer requests, so we need to instantiate it early. - public void onAllServicesInitialized() { - log.trace("onAllServicesInitialized"); - } - private void initPendingTrades() { if (firstPeerAuthenticatedListener != null) p2PService.removeP2PServiceListener(firstPeerAuthenticatedListener); diff --git a/core/src/main/java/io/bitsquare/trade/protocol/placeoffer/tasks/BroadcastCreateOfferFeeTx.java b/core/src/main/java/io/bitsquare/trade/protocol/placeoffer/tasks/BroadcastCreateOfferFeeTx.java index b9466a7a5c..714e0c0175 100644 --- a/core/src/main/java/io/bitsquare/trade/protocol/placeoffer/tasks/BroadcastCreateOfferFeeTx.java +++ b/core/src/main/java/io/bitsquare/trade/protocol/placeoffer/tasks/BroadcastCreateOfferFeeTx.java @@ -44,7 +44,7 @@ public class BroadcastCreateOfferFeeTx extends Task { protected void run() { try { runInterceptHook(); - Coin totalsNeeded = FeePolicy.SECURITY_DEPOSIT.add(FeePolicy.CREATE_OFFER_FEE).add(FeePolicy.TX_FEE); + Coin totalsNeeded = FeePolicy.getSecurityDeposit().add(FeePolicy.getCreateOfferFee()).add(FeePolicy.getFixedTxFeeForTrades()); AddressEntry addressEntry = model.walletService.getAddressEntryByOfferId(model.offer.getId()); Coin balance = model.walletService.getBalanceForAddress(addressEntry.getAddress()); if (balance.compareTo(totalsNeeded) >= 0) { diff --git a/core/src/main/java/io/bitsquare/trade/protocol/placeoffer/tasks/CreateOfferFeeTx.java b/core/src/main/java/io/bitsquare/trade/protocol/placeoffer/tasks/CreateOfferFeeTx.java index 930aa2dfc6..e85c10148a 100644 --- a/core/src/main/java/io/bitsquare/trade/protocol/placeoffer/tasks/CreateOfferFeeTx.java +++ b/core/src/main/java/io/bitsquare/trade/protocol/placeoffer/tasks/CreateOfferFeeTx.java @@ -45,7 +45,7 @@ public class CreateOfferFeeTx extends Task { Arbitrator selectedArbitrator = model.user.getAcceptedArbitratorByAddress(selectedArbitratorAddress); Transaction transaction = model.tradeWalletService.createTradingFeeTx( model.walletService.getAddressEntryByOfferId(model.offer.getId()), - FeePolicy.CREATE_OFFER_FEE, + FeePolicy.getCreateOfferFee(), selectedArbitrator.getBtcAddress()); // We assume there will be no tx malleability. We add a check later in case the published offer has a different hash. diff --git a/core/src/main/java/io/bitsquare/trade/protocol/trade/tasks/buyer/CreateAndSignDepositTxAsBuyer.java b/core/src/main/java/io/bitsquare/trade/protocol/trade/tasks/buyer/CreateAndSignDepositTxAsBuyer.java index b8e05d33bb..edca8c87d4 100644 --- a/core/src/main/java/io/bitsquare/trade/protocol/trade/tasks/buyer/CreateAndSignDepositTxAsBuyer.java +++ b/core/src/main/java/io/bitsquare/trade/protocol/trade/tasks/buyer/CreateAndSignDepositTxAsBuyer.java @@ -41,8 +41,8 @@ public class CreateAndSignDepositTxAsBuyer extends TradeTask { try { runInterceptHook(); checkNotNull(trade.getTradeAmount(), "trade.getTradeAmount() must not be null"); - Coin buyerInputAmount = FeePolicy.SECURITY_DEPOSIT.add(FeePolicy.TX_FEE); - Coin msOutputAmount = buyerInputAmount.add(FeePolicy.SECURITY_DEPOSIT).add(trade.getTradeAmount()); + Coin buyerInputAmount = FeePolicy.getSecurityDeposit().add(FeePolicy.getFixedTxFeeForTrades()); + Coin msOutputAmount = buyerInputAmount.add(FeePolicy.getSecurityDeposit()).add(trade.getTradeAmount()); log.info("\n\n------------------------------------------------------------\n" + "Contract as json\n" diff --git a/core/src/main/java/io/bitsquare/trade/protocol/trade/tasks/buyer/CreateDepositTxInputsAsBuyer.java b/core/src/main/java/io/bitsquare/trade/protocol/trade/tasks/buyer/CreateDepositTxInputsAsBuyer.java index 23b9821085..c399bdce1d 100644 --- a/core/src/main/java/io/bitsquare/trade/protocol/trade/tasks/buyer/CreateDepositTxInputsAsBuyer.java +++ b/core/src/main/java/io/bitsquare/trade/protocol/trade/tasks/buyer/CreateDepositTxInputsAsBuyer.java @@ -37,7 +37,7 @@ public class CreateDepositTxInputsAsBuyer extends TradeTask { protected void run() { try { runInterceptHook(); - Coin takerInputAmount = FeePolicy.SECURITY_DEPOSIT.add(FeePolicy.TX_FEE); + Coin takerInputAmount = FeePolicy.getSecurityDeposit().add(FeePolicy.getFixedTxFeeForTrades()); InputsAndChangeOutput result = processModel.getTradeWalletService().takerCreatesDepositsTxInputs(takerInputAmount, processModel.getAddressEntry()); processModel.setRawInputs(result.rawInputs); processModel.setChangeOutputValue(result.changeOutputValue); diff --git a/core/src/main/java/io/bitsquare/trade/protocol/trade/tasks/buyer/SignAndFinalizePayoutTx.java b/core/src/main/java/io/bitsquare/trade/protocol/trade/tasks/buyer/SignAndFinalizePayoutTx.java index e59f1fe406..5f8018fcd2 100644 --- a/core/src/main/java/io/bitsquare/trade/protocol/trade/tasks/buyer/SignAndFinalizePayoutTx.java +++ b/core/src/main/java/io/bitsquare/trade/protocol/trade/tasks/buyer/SignAndFinalizePayoutTx.java @@ -40,7 +40,7 @@ public class SignAndFinalizePayoutTx extends TradeTask { try { runInterceptHook(); checkNotNull(trade.getTradeAmount(), "trade.getTradeAmount() must not be null"); - Coin sellerPayoutAmount = FeePolicy.SECURITY_DEPOSIT; + Coin sellerPayoutAmount = FeePolicy.getSecurityDeposit(); Coin buyerPayoutAmount = sellerPayoutAmount.add(trade.getTradeAmount()); Transaction transaction = processModel.getTradeWalletService().buyerSignsAndFinalizesPayoutTx( diff --git a/core/src/main/java/io/bitsquare/trade/protocol/trade/tasks/seller/CreateAndSignDepositTxAsSeller.java b/core/src/main/java/io/bitsquare/trade/protocol/trade/tasks/seller/CreateAndSignDepositTxAsSeller.java index 37e32fc66b..e7bb35f75f 100644 --- a/core/src/main/java/io/bitsquare/trade/protocol/trade/tasks/seller/CreateAndSignDepositTxAsSeller.java +++ b/core/src/main/java/io/bitsquare/trade/protocol/trade/tasks/seller/CreateAndSignDepositTxAsSeller.java @@ -41,8 +41,8 @@ public class CreateAndSignDepositTxAsSeller extends TradeTask { try { runInterceptHook(); checkNotNull(trade.getTradeAmount(), "trade.getTradeAmount() must not be null"); - Coin sellerInputAmount = FeePolicy.SECURITY_DEPOSIT.add(FeePolicy.TX_FEE).add(trade.getTradeAmount()); - Coin msOutputAmount = sellerInputAmount.add(FeePolicy.SECURITY_DEPOSIT); + Coin sellerInputAmount = FeePolicy.getSecurityDeposit().add(FeePolicy.getFixedTxFeeForTrades()).add(trade.getTradeAmount()); + Coin msOutputAmount = sellerInputAmount.add(FeePolicy.getSecurityDeposit()); log.info("\n\n------------------------------------------------------------\n" + "Contract as json\n" diff --git a/core/src/main/java/io/bitsquare/trade/protocol/trade/tasks/seller/CreateDepositTxInputsAsSeller.java b/core/src/main/java/io/bitsquare/trade/protocol/trade/tasks/seller/CreateDepositTxInputsAsSeller.java index f523595c97..dc8d33faab 100644 --- a/core/src/main/java/io/bitsquare/trade/protocol/trade/tasks/seller/CreateDepositTxInputsAsSeller.java +++ b/core/src/main/java/io/bitsquare/trade/protocol/trade/tasks/seller/CreateDepositTxInputsAsSeller.java @@ -38,7 +38,7 @@ public class CreateDepositTxInputsAsSeller extends TradeTask { try { runInterceptHook(); if (trade.getTradeAmount() != null) { - Coin takerInputAmount = FeePolicy.SECURITY_DEPOSIT.add(FeePolicy.TX_FEE).add(trade.getTradeAmount()); + Coin takerInputAmount = FeePolicy.getSecurityDeposit().add(FeePolicy.getFixedTxFeeForTrades()).add(trade.getTradeAmount()); InputsAndChangeOutput result = processModel.getTradeWalletService().takerCreatesDepositsTxInputs(takerInputAmount, processModel .getAddressEntry()); diff --git a/core/src/main/java/io/bitsquare/trade/protocol/trade/tasks/seller/SignPayoutTx.java b/core/src/main/java/io/bitsquare/trade/protocol/trade/tasks/seller/SignPayoutTx.java index 221717864b..02de5de774 100644 --- a/core/src/main/java/io/bitsquare/trade/protocol/trade/tasks/seller/SignPayoutTx.java +++ b/core/src/main/java/io/bitsquare/trade/protocol/trade/tasks/seller/SignPayoutTx.java @@ -39,7 +39,7 @@ public class SignPayoutTx extends TradeTask { try { runInterceptHook(); checkNotNull(trade.getTradeAmount(), "trade.getTradeAmount() must not be null"); - Coin sellerPayoutAmount = FeePolicy.SECURITY_DEPOSIT; + Coin sellerPayoutAmount = FeePolicy.getSecurityDeposit(); Coin buyerPayoutAmount = sellerPayoutAmount.add(trade.getTradeAmount()); // We use the sellers LastBlockSeenHeight, which might be different to the buyers one. diff --git a/core/src/main/java/io/bitsquare/trade/protocol/trade/tasks/taker/CreateTakeOfferFeeTx.java b/core/src/main/java/io/bitsquare/trade/protocol/trade/tasks/taker/CreateTakeOfferFeeTx.java index 7f59399202..7d2908ace3 100644 --- a/core/src/main/java/io/bitsquare/trade/protocol/trade/tasks/taker/CreateTakeOfferFeeTx.java +++ b/core/src/main/java/io/bitsquare/trade/protocol/trade/tasks/taker/CreateTakeOfferFeeTx.java @@ -47,7 +47,7 @@ public class CreateTakeOfferFeeTx extends TradeTask { Arbitrator selectedArbitrator = user.getAcceptedArbitratorByAddress(selectedArbitratorAddress); Transaction createTakeOfferFeeTx = processModel.getTradeWalletService().createTradingFeeTx( processModel.getAddressEntry(), - FeePolicy.TAKE_OFFER_FEE, + FeePolicy.getTakeOfferFee(), selectedArbitrator.getBtcAddress()); processModel.setTakeOfferFeeTx(createTakeOfferFeeTx); diff --git a/core/src/main/java/io/bitsquare/user/Preferences.java b/core/src/main/java/io/bitsquare/user/Preferences.java index 4bdc2e8aae..0c763ff3e1 100644 --- a/core/src/main/java/io/bitsquare/user/Preferences.java +++ b/core/src/main/java/io/bitsquare/user/Preferences.java @@ -20,6 +20,7 @@ package io.bitsquare.user; import io.bitsquare.app.BitsquareEnvironment; import io.bitsquare.app.Version; import io.bitsquare.btc.BitcoinNetwork; +import io.bitsquare.btc.FeePolicy; import io.bitsquare.locale.CountryUtil; import io.bitsquare.locale.CurrencyUtil; import io.bitsquare.locale.TradeCurrency; @@ -31,6 +32,8 @@ import javafx.beans.property.SimpleStringProperty; import javafx.beans.property.StringProperty; import javafx.collections.FXCollections; import javafx.collections.ObservableList; +import org.bitcoinj.core.Coin; +import org.bitcoinj.core.Transaction; import org.bitcoinj.utils.MonetaryFormat; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -100,6 +103,7 @@ public class Preferences implements Serializable { private boolean tacAccepted; private Locale preferredLocale; private TradeCurrency preferredTradeCurrency; + private long txFeePerKB = FeePolicy.getFeePerKb().value; // Observable wrappers transient private final StringProperty btcDenominationProperty = new SimpleStringProperty(btcDenomination); @@ -146,6 +150,11 @@ public class Preferences implements Serializable { defaultLocale = preferredLocale; preferredTradeCurrency = persisted.getPreferredTradeCurrency(); defaultTradeCurrency = preferredTradeCurrency; + try { + setTxFeePerKB(persisted.getTxFeePerKB()); + } catch (Exception e) { + // leave default value + } } else { setTradeCurrencies(CurrencyUtil.getAllSortedCurrencies()); tradeCurrencies = new ArrayList<>(tradeCurrenciesAsObservable); @@ -162,6 +171,7 @@ public class Preferences implements Serializable { preferredLocale = getDefaultLocale(); preferredTradeCurrency = getDefaultTradeCurrency(); + storage.queueUpForSave(); } @@ -272,6 +282,14 @@ public class Preferences implements Serializable { storage.queueUpForSave(); } + public void setTxFeePerKB(long txFeePerKB) throws Exception { + if (txFeePerKB < Transaction.REFERENCE_DEFAULT_MIN_TX_FEE.value) + throw new Exception("Transaction fee must be at least 5 satoshi/byte"); + + this.txFeePerKB = txFeePerKB; + FeePolicy.setFeePerKb(Coin.valueOf(txFeePerKB)); + storage.queueUpForSave(); + } /////////////////////////////////////////////////////////////////////////////////////////// // Getter @@ -387,4 +405,8 @@ public class Preferences implements Serializable { public TradeCurrency getPreferredTradeCurrency() { return preferredTradeCurrency; } + + public long getTxFeePerKB() { + return Math.max(Transaction.REFERENCE_DEFAULT_MIN_TX_FEE.value, txFeePerKB); + } } diff --git a/core/src/test/java/io/bitsquare/btc/RestrictionsTest.java b/core/src/test/java/io/bitsquare/btc/RestrictionsTest.java index 1ebfb62889..7b42b4df24 100644 --- a/core/src/test/java/io/bitsquare/btc/RestrictionsTest.java +++ b/core/src/test/java/io/bitsquare/btc/RestrictionsTest.java @@ -28,21 +28,21 @@ public class RestrictionsTest { @Test public void testIsMinSpendableAmount() { Coin amount = null; - assertFalse("tx unfunded, pending", Restrictions.isMinSpendableAmount(amount)); + assertFalse("tx unfunded, pending", Restrictions.isAboveFixedTxFeeAndDust(amount)); amount = Coin.ZERO; - assertFalse("tx unfunded, pending", Restrictions.isMinSpendableAmount(amount)); + assertFalse("tx unfunded, pending", Restrictions.isAboveFixedTxFeeAndDust(amount)); - amount = FeePolicy.TX_FEE; - assertFalse("tx unfunded, pending", Restrictions.isMinSpendableAmount(amount)); + amount = FeePolicy.getFixedTxFeeForTrades(); + assertFalse("tx unfunded, pending", Restrictions.isAboveFixedTxFeeAndDust(amount)); amount = Transaction.MIN_NONDUST_OUTPUT; - assertFalse("tx unfunded, pending", Restrictions.isMinSpendableAmount(amount)); + assertFalse("tx unfunded, pending", Restrictions.isAboveFixedTxFeeAndDust(amount)); - amount = FeePolicy.TX_FEE.add(Transaction.MIN_NONDUST_OUTPUT); - assertFalse("tx unfunded, pending", Restrictions.isMinSpendableAmount(amount)); + amount = FeePolicy.getFixedTxFeeForTrades().add(Transaction.MIN_NONDUST_OUTPUT); + assertFalse("tx unfunded, pending", Restrictions.isAboveFixedTxFeeAndDust(amount)); - amount = FeePolicy.TX_FEE.add(Transaction.MIN_NONDUST_OUTPUT).add(Coin.valueOf(1)); - assertTrue("tx unfunded, pending", Restrictions.isMinSpendableAmount(amount)); + amount = FeePolicy.getFixedTxFeeForTrades().add(Transaction.MIN_NONDUST_OUTPUT).add(Coin.valueOf(1)); + assertTrue("tx unfunded, pending", Restrictions.isAboveFixedTxFeeAndDust(amount)); } } diff --git a/gui/src/main/java/io/bitsquare/gui/main/MainView.java b/gui/src/main/java/io/bitsquare/gui/main/MainView.java index 1cc9028b2d..3958cf88d8 100644 --- a/gui/src/main/java/io/bitsquare/gui/main/MainView.java +++ b/gui/src/main/java/io/bitsquare/gui/main/MainView.java @@ -195,7 +195,7 @@ public class MainView extends InitializableView { private Tuple2 getBalanceBox(String text) { TextField textField = new TextField(); textField.setEditable(false); - textField.setPrefWidth(100); + textField.setPrefWidth(120); textField.setMouseTransparent(true); textField.setFocusTraversable(false); textField.setStyle("-fx-alignment: center; -fx-background-color: white;"); diff --git a/gui/src/main/java/io/bitsquare/gui/main/MainViewModel.java b/gui/src/main/java/io/bitsquare/gui/main/MainViewModel.java index d35af335d2..ea7e3fdc8b 100644 --- a/gui/src/main/java/io/bitsquare/gui/main/MainViewModel.java +++ b/gui/src/main/java/io/bitsquare/gui/main/MainViewModel.java @@ -313,7 +313,6 @@ public class MainViewModel implements ViewModel { }); pendingTradesChanged(); addDisputeStateListeners(tradeManager.getTrades()); - tradeManager.onAllServicesInitialized(); // arbitratorManager diff --git a/gui/src/main/java/io/bitsquare/gui/main/disputes/trader/DisputeSummaryPopup.java b/gui/src/main/java/io/bitsquare/gui/main/disputes/trader/DisputeSummaryPopup.java index ce563fe244..055bcd880a 100644 --- a/gui/src/main/java/io/bitsquare/gui/main/disputes/trader/DisputeSummaryPopup.java +++ b/gui/src/main/java/io/bitsquare/gui/main/disputes/trader/DisputeSummaryPopup.java @@ -406,7 +406,7 @@ public class DisputeSummaryPopup extends Popup { private void calculatePayoutAmounts(DisputeResult.FeePaymentPolicy feePayment) { Contract contract = dispute.getContract(); - Coin refund = FeePolicy.SECURITY_DEPOSIT; + Coin refund = FeePolicy.getSecurityDeposit(); Coin winnerRefund; Coin loserRefund; switch (feePayment) { diff --git a/gui/src/main/java/io/bitsquare/gui/main/funds/reserved/ReservedListItem.java b/gui/src/main/java/io/bitsquare/gui/main/funds/reserved/ReservedListItem.java index d8520e0ffc..20feb7426f 100644 --- a/gui/src/main/java/io/bitsquare/gui/main/funds/reserved/ReservedListItem.java +++ b/gui/src/main/java/io/bitsquare/gui/main/funds/reserved/ReservedListItem.java @@ -105,28 +105,29 @@ public class ReservedListItem { switch (phase) { case PREPARATION: case TAKER_FEE_PAID: - balanceLabel.setText(formatter.formatCoinWithCode(balance) + " locked in deposit"); + balanceLabel.setText(formatter.formatCoinWithCode(balance) + " (locally reserved)"); break; case DEPOSIT_REQUESTED: case DEPOSIT_PAID: case FIAT_SENT: case FIAT_RECEIVED: // We ignore the tx fee as it will be paid by both (once deposit, once payout) - Coin balanceInDeposit = FeePolicy.SECURITY_DEPOSIT; + Coin balanceInDeposit = FeePolicy.getSecurityDeposit(); // For the seller we add the trade amount if (trade.getContract().getSellerAddress().equals(getAddress())) balanceInDeposit.add(trade.getTradeAmount()); - balanceLabel.setText(formatter.formatCoinWithCode(balanceInDeposit) + " locked in deposit"); + + balanceLabel.setText(formatter.formatCoinWithCode(balance) + " (in MS escrow)"); break; case PAYOUT_PAID: - balanceLabel.setText(formatter.formatCoinWithCode(balance) + " in wallet"); + balanceLabel.setText(formatter.formatCoinWithCode(balance) + " (in local wallet)"); break; case WITHDRAWN: log.error("Invalid state at updateBalance (WITHDRAWN)"); balanceLabel.setText(formatter.formatCoinWithCode(balance) + " already withdrawn"); break; case DISPUTE: - balanceLabel.setText(formatter.formatCoinWithCode(balance) + " locked because of open ticket"); + balanceLabel.setText(formatter.formatCoinWithCode(balance) + " open dispute/ticket"); break; default: log.warn("Not supported tradePhase: " + phase); diff --git a/gui/src/main/java/io/bitsquare/gui/main/funds/withdrawal/WithdrawalView.java b/gui/src/main/java/io/bitsquare/gui/main/funds/withdrawal/WithdrawalView.java index cc3c281c66..5083810da6 100644 --- a/gui/src/main/java/io/bitsquare/gui/main/funds/withdrawal/WithdrawalView.java +++ b/gui/src/main/java/io/bitsquare/gui/main/funds/withdrawal/WithdrawalView.java @@ -20,7 +20,6 @@ package io.bitsquare.gui.main.funds.withdrawal; import com.google.common.util.concurrent.FutureCallback; import io.bitsquare.app.BitsquareApp; import io.bitsquare.btc.AddressEntry; -import io.bitsquare.btc.FeePolicy; import io.bitsquare.btc.Restrictions; import io.bitsquare.btc.WalletService; import io.bitsquare.btc.listeners.BalanceListener; @@ -65,9 +64,12 @@ import java.util.stream.Stream; @FxmlView public class WithdrawalView extends ActivatableView { - @FXML Button withdrawButton; - @FXML TableView table; - @FXML TextField withdrawFromTextField, withdrawToTextField, amountTextField; + @FXML + Button withdrawButton; + @FXML + TableView table; + @FXML + TextField withdrawFromTextField, withdrawToTextField, amountTextField; @FXML TableColumn labelColumn, addressColumn, balanceColumn, confidenceColumn; @@ -114,7 +116,7 @@ public class WithdrawalView extends ActivatableView { setConfidenceColumnCellFactory(); if (BitsquareApp.DEV_MODE) - withdrawToTextField.setText("mwajQdfYnve1knXnmv7JdeiVpeogTsck6S"); + withdrawToTextField.setText("mxAkWWaQBqwqcYstKzqLku3kzR6pbu2zHq"); } private boolean areInputsValid() { @@ -144,8 +146,7 @@ public class WithdrawalView extends ActivatableView { if (Coin.ZERO.compareTo(newValue.getBalance()) <= 0) { amountTextField.setText(newValue.getBalance().toPlainString()); withdrawFromTextField.setText(newValue.getAddressEntry().getAddressString()); - } - else { + } else { withdrawFromTextField.setText(""); withdrawFromTextField.setPromptText("No fund to withdrawal on that address."); amountTextField.setText(""); @@ -173,15 +174,14 @@ public class WithdrawalView extends ActivatableView { @FXML public void onWithdraw() { - Coin amount = formatter.parseToCoin(amountTextField.getText()); - if (Restrictions.isMinSpendableAmount(amount)) { + Coin senderAmount = formatter.parseToCoin(amountTextField.getText()); + if (Restrictions.isAboveDust(senderAmount)) { FutureCallback callback = new FutureCallback() { @Override public void onSuccess(@javax.annotation.Nullable Transaction transaction) { if (transaction != null) { log.info("onWithdraw onSuccess tx ID:" + transaction.getHashAsString()); - } - else { + } else { log.error("onWithdraw transaction is null"); } } @@ -191,26 +191,29 @@ public class WithdrawalView extends ActivatableView { log.error("onWithdraw onFailure"); } }; + try { + Coin requiredFee = walletService.getRequiredFee(withdrawFromTextField.getText(), + withdrawToTextField.getText(), senderAmount); + Coin receiverAmount = senderAmount.subtract(requiredFee); + if (BitsquareApp.DEV_MODE) { + doWithdraw(receiverAmount, callback); + } else { + new Popup().headLine("Confirm your withdrawal request") + .message("Sending: " + formatter.formatCoinWithCode(senderAmount) + "\n" + + "From address: " + withdrawFromTextField.getText() + "\n" + + "To receiving address: " + withdrawToTextField.getText() + ".\n\n" + + "Required transaction fee is: " + formatter.formatCoinWithCode(requiredFee) + "\n" + + "Recipient will receive: " + formatter.formatCoinWithCode(receiverAmount) + "\n\n" + + "Are you sure you want to withdraw that amount?") + .onAction(() -> doWithdraw(receiverAmount, callback)) + .show(); - if (BitsquareApp.DEV_MODE) { - doWithdraw(amount, callback); - } else { - new Popup().headLine("Confirm your withdrawal request").message("Amount: " + amountTextField.getText() + " " + - "BTC\n" + - "Sending" + - " address: " + withdrawFromTextField.getText() + "\n" + "Receiving address: " + - withdrawToTextField.getText() + "\n" + "Transaction fee: " + - formatter.formatCoinWithCode(FeePolicy.TX_FEE) + "\n" + - "Receivers amount: " + - formatter.formatCoinWithCode(amount.subtract(FeePolicy.TX_FEE)) + " BTC\n\n" + - "Are you sure you want to withdraw that amount?") - .onAction(() -> { - doWithdraw(amount, callback); - }) - .show(); + } + } catch (AddressFormatException | InsufficientMoneyException e) { + e.printStackTrace(); + log.error(e.getMessage()); } - } - else { + } else { new Popup().warning("The amount to transfer is lower than the transaction fee and the min. possible tx value.").show(); } } @@ -284,8 +287,7 @@ public class WithdrawalView extends ActivatableView { hyperlink.setOnAction(event -> openDetails(item)); } setGraphic(hyperlink); - } - else { + } else { setGraphic(null); setId(null); } @@ -363,8 +365,7 @@ public class WithdrawalView extends ActivatableView { if (item != null && !empty) { setGraphic(item.getProgressIndicator()); - } - else { + } else { setGraphic(null); } } diff --git a/gui/src/main/java/io/bitsquare/gui/main/offer/createoffer/CreateOfferDataModel.java b/gui/src/main/java/io/bitsquare/gui/main/offer/createoffer/CreateOfferDataModel.java index f12700f172..e2799d588a 100644 --- a/gui/src/main/java/io/bitsquare/gui/main/offer/createoffer/CreateOfferDataModel.java +++ b/gui/src/main/java/io/bitsquare/gui/main/offer/createoffer/CreateOfferDataModel.java @@ -117,9 +117,9 @@ class CreateOfferDataModel extends ActivatableDataModel { offerId = UUID.randomUUID().toString(); addressEntry = walletService.getAddressEntryByOfferId(offerId); - offerFeeAsCoin = FeePolicy.CREATE_OFFER_FEE; - networkFeeAsCoin = FeePolicy.TX_FEE; - securityDepositAsCoin = FeePolicy.SECURITY_DEPOSIT; + offerFeeAsCoin = FeePolicy.getCreateOfferFee(); + networkFeeAsCoin = FeePolicy.getFixedTxFeeForTrades(); + securityDepositAsCoin = FeePolicy.getSecurityDeposit(); balanceListener = new BalanceListener(getAddressEntry().getAddress()) { @Override diff --git a/gui/src/main/java/io/bitsquare/gui/main/offer/createoffer/CreateOfferView.java b/gui/src/main/java/io/bitsquare/gui/main/offer/createoffer/CreateOfferView.java index 9fca480304..69cfe72e82 100644 --- a/gui/src/main/java/io/bitsquare/gui/main/offer/createoffer/CreateOfferView.java +++ b/gui/src/main/java/io/bitsquare/gui/main/offer/createoffer/CreateOfferView.java @@ -436,7 +436,7 @@ public class CreateOfferView extends ActivatableViewAndModel { new Popup().headLine(BSResources.get("createOffer.success.headline")) .message(BSResources.get("createOffer.success.info")) - .actionButtonText("Go to \"Open offers\"") + .actionButtonText("Go to \"My offers\"") .onAction(() -> { close(); FxTimer.runLater(Duration.ofMillis(100), diff --git a/gui/src/main/java/io/bitsquare/gui/main/offer/takeoffer/TakeOfferDataModel.java b/gui/src/main/java/io/bitsquare/gui/main/offer/takeoffer/TakeOfferDataModel.java index 5c9e1dd6f8..ff6ba3ab3a 100644 --- a/gui/src/main/java/io/bitsquare/gui/main/offer/takeoffer/TakeOfferDataModel.java +++ b/gui/src/main/java/io/bitsquare/gui/main/offer/takeoffer/TakeOfferDataModel.java @@ -20,7 +20,10 @@ package io.bitsquare.gui.main.offer.takeoffer; import com.google.inject.Inject; import io.bitsquare.app.BitsquareApp; import io.bitsquare.arbitration.Arbitrator; -import io.bitsquare.btc.*; +import io.bitsquare.btc.AddressEntry; +import io.bitsquare.btc.FeePolicy; +import io.bitsquare.btc.TradeWalletService; +import io.bitsquare.btc.WalletService; import io.bitsquare.btc.listeners.BalanceListener; import io.bitsquare.common.handlers.ResultHandler; import io.bitsquare.gui.common.model.ActivatableDataModel; @@ -94,9 +97,9 @@ class TakeOfferDataModel extends ActivatableDataModel { this.walletPasswordPopup = walletPasswordPopup; this.preferences = preferences; - offerFeeAsCoin = FeePolicy.CREATE_OFFER_FEE; - networkFeeAsCoin = FeePolicy.TX_FEE; - securityDepositAsCoin = FeePolicy.SECURITY_DEPOSIT; + offerFeeAsCoin = FeePolicy.getCreateOfferFee(); + networkFeeAsCoin = FeePolicy.getFixedTxFeeForTrades(); + securityDepositAsCoin = FeePolicy.getSecurityDeposit(); } @Override @@ -127,7 +130,7 @@ class TakeOfferDataModel extends ActivatableDataModel { addressEntry = walletService.getAddressEntryByOfferId(offer.getId()); checkNotNull(addressEntry, "addressEntry must not be null"); - + ObservableList possiblePaymentAccounts = getPossiblePaymentAccounts(); checkArgument(!possiblePaymentAccounts.isEmpty(), "possiblePaymentAccounts.isEmpty()"); paymentAccount = possiblePaymentAccounts.get(0); @@ -135,7 +138,7 @@ class TakeOfferDataModel extends ActivatableDataModel { amountAsCoin.set(offer.getAmount()); if (BitsquareApp.DEV_MODE) - amountAsCoin.set(Restrictions.MIN_TRADE_AMOUNT); + amountAsCoin.set(offer.getAmount()); calculateVolume(); calculateTotalToPay(); @@ -274,7 +277,7 @@ class TakeOfferDataModel extends ActivatableDataModel { boolean isMinAmountLessOrEqualAmount() { //noinspection SimplifiableIfStatement - if (offer != null && offer.getMinAmount() != null && amountAsCoin.get() != null) + if (offer != null && offer.getMinAmount() != null && amountAsCoin.get() != null) return !offer.getMinAmount().isGreaterThan(amountAsCoin.get()); return true; } diff --git a/gui/src/main/java/io/bitsquare/gui/main/offer/takeoffer/TakeOfferView.java b/gui/src/main/java/io/bitsquare/gui/main/offer/takeoffer/TakeOfferView.java index 88a0465034..1e084d79d5 100644 --- a/gui/src/main/java/io/bitsquare/gui/main/offer/takeoffer/TakeOfferView.java +++ b/gui/src/main/java/io/bitsquare/gui/main/offer/takeoffer/TakeOfferView.java @@ -196,7 +196,7 @@ public class TakeOfferView extends ActivatableViewAndModel a == null && b == null && !c && !d) .subscribe((observable, oldValue, newValue) -> { - if (newValue) { + if (!oldValue && newValue) { isOfferAvailablePopup = new Popup().information(BSResources.get("takeOffer.fundsBox.isOfferAvailable")) .show() .onClose(() -> { diff --git a/gui/src/main/java/io/bitsquare/gui/main/portfolio/PortfolioView.fxml b/gui/src/main/java/io/bitsquare/gui/main/portfolio/PortfolioView.fxml index 3b362ca950..6067d295dc 100644 --- a/gui/src/main/java/io/bitsquare/gui/main/portfolio/PortfolioView.fxml +++ b/gui/src/main/java/io/bitsquare/gui/main/portfolio/PortfolioView.fxml @@ -17,14 +17,15 @@ ~ along with Bitsquare. If not, see . --> - - + + + - + diff --git a/gui/src/main/java/io/bitsquare/gui/main/portfolio/pendingtrades/PendingTradesDataModel.java b/gui/src/main/java/io/bitsquare/gui/main/portfolio/pendingtrades/PendingTradesDataModel.java index 2bcf252a02..923faf334e 100644 --- a/gui/src/main/java/io/bitsquare/gui/main/portfolio/pendingtrades/PendingTradesDataModel.java +++ b/gui/src/main/java/io/bitsquare/gui/main/portfolio/pendingtrades/PendingTradesDataModel.java @@ -295,7 +295,7 @@ public class PendingTradesDataModel extends ActivatableDataModel { } Coin getTotalFees() { - return FeePolicy.TX_FEE.add(isOfferer() ? FeePolicy.CREATE_OFFER_FEE : FeePolicy.TAKE_OFFER_FEE); + return FeePolicy.getFixedTxFeeForTrades().add(isOfferer() ? FeePolicy.getCreateOfferFee() : FeePolicy.getTakeOfferFee()); } PendingTradesListItem getSelectedItem() { diff --git a/gui/src/main/java/io/bitsquare/gui/main/portfolio/pendingtrades/PendingTradesViewModel.java b/gui/src/main/java/io/bitsquare/gui/main/portfolio/pendingtrades/PendingTradesViewModel.java index f655eb829d..a00fe82702 100644 --- a/gui/src/main/java/io/bitsquare/gui/main/portfolio/pendingtrades/PendingTradesViewModel.java +++ b/gui/src/main/java/io/bitsquare/gui/main/portfolio/pendingtrades/PendingTradesViewModel.java @@ -324,7 +324,7 @@ public class PendingTradesViewModel extends ActivatableWithDataModel transactionFeeFocusedListener; @Inject public PreferencesView(PreferencesViewModel model) { @@ -52,12 +56,16 @@ public class PreferencesView extends ActivatableViewAndModel { + model.onFocusOutTransactionFeeTextField(oldValue, newValue, transactionFeeInputTextField.getText()); + }; + addTitledGroupBg(root, ++gridRow, 5, "Display options", Layout.GROUP_DISTANCE); useAnimationsCheckBox = addLabelCheckBox(root, gridRow, "Use animations:", "", Layout.FIRST_ROW_AND_GROUP_DISTANCE).second; useEffectsCheckBox = addLabelCheckBox(root, ++gridRow, "Use effects:", "").second; @@ -119,6 +127,8 @@ public class PreferencesView extends ActivatableViewAndModel model.onSelectBlockExplorer(blockExplorerComboBox.getSelectionModel().getSelectedItem())); + transactionFeeInputTextField.textProperty().bindBidirectional(model.transactionFeePerByte); + transactionFeeInputTextField.focusedProperty().addListener(transactionFeeFocusedListener); useAnimationsCheckBox.setSelected(model.getUseAnimations()); useAnimationsCheckBox.setOnAction(e -> model.onSelectUseAnimations(useAnimationsCheckBox.isSelected())); @@ -142,6 +152,8 @@ public class PreferencesView extends ActivatableViewAndModel blockExplorers; final ObservableList tradeCurrencies; final ObservableList languageCodes; - + final StringProperty transactionFeePerByte = new SimpleStringProperty(); + /////////////////////////////////////////////////////////////////////////////////////////// // Constructor, initialisation @@ -52,13 +58,13 @@ class PreferencesViewModel extends ActivatableViewModel { @Override protected void activate() { + transactionFeePerByte.set(String.valueOf(preferences.getTxFeePerKB() / 1000)); } @Override protected void deactivate() { } - /////////////////////////////////////////////////////////////////////////////////////////// // UI actions /////////////////////////////////////////////////////////////////////////////////////////// @@ -99,6 +105,19 @@ class PreferencesViewModel extends ActivatableViewModel { preferences.setPreferredLocale(new Locale(code, preferences.getPreferredLocale().getCountry())); } + public void onFocusOutTransactionFeeTextField(Boolean oldValue, Boolean newValue, String text) { + if (oldValue && !newValue) { + try { + preferences.setTxFeePerKB(Long.valueOf(transactionFeePerByte.get()) * 1000); + } catch (Exception e) { + new Popup().warning(e.getMessage()) + .onClose(() -> UserThread.runAfter( + () -> transactionFeePerByte.set(String.valueOf(preferences.getTxFeePerKB() / 1000)), + 100, TimeUnit.MILLISECONDS)) + .show(); + } + } + } /////////////////////////////////////////////////////////////////////////////////////////// // Getters @@ -139,4 +158,5 @@ class PreferencesViewModel extends ActivatableViewModel { public TradeCurrency getTradeCurrency() { return preferences.getPreferredTradeCurrency(); } + } diff --git a/gui/src/main/java/io/bitsquare/gui/popups/EmptyWalletPopup.java b/gui/src/main/java/io/bitsquare/gui/popups/EmptyWalletPopup.java index e90985b99f..2a6aee13c4 100644 --- a/gui/src/main/java/io/bitsquare/gui/popups/EmptyWalletPopup.java +++ b/gui/src/main/java/io/bitsquare/gui/popups/EmptyWalletPopup.java @@ -99,7 +99,7 @@ public class EmptyWalletPopup extends Popup { Tuple2 tuple = addLabelInputTextField(gridPane, ++rowIndex, "Your destination address:"); addressInputTextField = tuple.second; emptyWalletButton = new Button("Empty wallet"); - boolean isBalanceSufficient = Restrictions.isMinSpendableAmount(totalBalance); + boolean isBalanceSufficient = Restrictions.isAboveDust(totalBalance); emptyWalletButton.setDefaultButton(isBalanceSufficient); closeButton.setDefaultButton(!isBalanceSufficient); emptyWalletButton.setDisable(!isBalanceSufficient && addressInputTextField.getText().length() > 0); diff --git a/gui/src/main/resources/i18n/displayStrings.properties b/gui/src/main/resources/i18n/displayStrings.properties index f97969ddee..472905f362 100644 --- a/gui/src/main/resources/i18n/displayStrings.properties +++ b/gui/src/main/resources/i18n/displayStrings.properties @@ -76,7 +76,7 @@ createOffer.advancedBox.county=Payments account country: createOffer.advancedBox.info=Your trading partners must fulfill your offer restrictions. You can edit the accepted countries, languages and arbitrators in the settings. The payments account details are used from your current selected payments account (if you have multiple payments accounts). createOffer.success.headline=Your offer has been published to the P2P network. -createOffer.success.info=You can manage your open offers in the \"Portfolio\" screen under \"Open offers\". +createOffer.success.info=You can manage your open offers in the \"Portfolio\" screen under \"My offers\". createOffer.error.message=An error occurred when placing the offer.\n\n{0}