mirror of
https://github.com/haveno-dex/haveno.git
synced 2025-04-19 15:26:03 -04:00
Refactor fee handling, add user defined fee to settings, UI fixes
This commit is contained in:
parent
35d6612820
commit
f723bf5737
@ -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
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}*/
|
||||
|
||||
}
|
||||
|
@ -438,15 +438,29 @@ public class WalletService {
|
||||
// Withdrawal
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
public String sendFunds(String fromAddress,
|
||||
String toAddress,
|
||||
Coin amount,
|
||||
KeyParameter aesKey,
|
||||
FutureCallback<Transaction> 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<Transaction> 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<Transaction>() {
|
||||
@Override
|
||||
public void onSuccess(Transaction result) {
|
||||
|
@ -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());
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
@ -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() {
|
||||
|
@ -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);
|
||||
|
||||
|
@ -44,7 +44,7 @@ public class BroadcastCreateOfferFeeTx extends Task<PlaceOfferModel> {
|
||||
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) {
|
||||
|
@ -45,7 +45,7 @@ public class CreateOfferFeeTx extends Task<PlaceOfferModel> {
|
||||
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.
|
||||
|
@ -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"
|
||||
|
@ -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);
|
||||
|
@ -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(
|
||||
|
@ -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"
|
||||
|
@ -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());
|
||||
|
@ -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.
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
@ -195,7 +195,7 @@ public class MainView extends InitializableView<StackPane, MainViewModel> {
|
||||
private Tuple2<TextField, VBox> 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;");
|
||||
|
@ -313,7 +313,6 @@ public class MainViewModel implements ViewModel {
|
||||
});
|
||||
pendingTradesChanged();
|
||||
addDisputeStateListeners(tradeManager.getTrades());
|
||||
tradeManager.onAllServicesInitialized();
|
||||
|
||||
|
||||
// arbitratorManager
|
||||
|
@ -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) {
|
||||
|
@ -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);
|
||||
|
@ -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<VBox, Void> {
|
||||
|
||||
@FXML Button withdrawButton;
|
||||
@FXML TableView<WithdrawalListItem> table;
|
||||
@FXML TextField withdrawFromTextField, withdrawToTextField, amountTextField;
|
||||
@FXML
|
||||
Button withdrawButton;
|
||||
@FXML
|
||||
TableView<WithdrawalListItem> table;
|
||||
@FXML
|
||||
TextField withdrawFromTextField, withdrawToTextField, amountTextField;
|
||||
@FXML
|
||||
TableColumn<WithdrawalListItem, WithdrawalListItem> labelColumn, addressColumn, balanceColumn, confidenceColumn;
|
||||
|
||||
@ -114,7 +116,7 @@ public class WithdrawalView extends ActivatableView<VBox, Void> {
|
||||
setConfidenceColumnCellFactory();
|
||||
|
||||
if (BitsquareApp.DEV_MODE)
|
||||
withdrawToTextField.setText("mwajQdfYnve1knXnmv7JdeiVpeogTsck6S");
|
||||
withdrawToTextField.setText("mxAkWWaQBqwqcYstKzqLku3kzR6pbu2zHq");
|
||||
}
|
||||
|
||||
private boolean areInputsValid() {
|
||||
@ -144,8 +146,7 @@ public class WithdrawalView extends ActivatableView<VBox, Void> {
|
||||
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<VBox, Void> {
|
||||
|
||||
@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<Transaction> callback = new FutureCallback<Transaction>() {
|
||||
@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<VBox, Void> {
|
||||
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<VBox, Void> {
|
||||
hyperlink.setOnAction(event -> openDetails(item));
|
||||
}
|
||||
setGraphic(hyperlink);
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
setGraphic(null);
|
||||
setId(null);
|
||||
}
|
||||
@ -363,8 +365,7 @@ public class WithdrawalView extends ActivatableView<VBox, Void> {
|
||||
|
||||
if (item != null && !empty) {
|
||||
setGraphic(item.getProgressIndicator());
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
setGraphic(null);
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -436,7 +436,7 @@ public class CreateOfferView extends ActivatableViewAndModel<AnchorPane, CreateO
|
||||
() -> {
|
||||
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),
|
||||
|
@ -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<PaymentAccount> 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;
|
||||
}
|
||||
|
@ -196,7 +196,7 @@ public class TakeOfferView extends ActivatableViewAndModel<AnchorPane, TakeOffer
|
||||
model.isOfferAvailable,
|
||||
(a, b, c, d) -> 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(() -> {
|
||||
|
@ -17,14 +17,15 @@
|
||||
~ along with Bitsquare. If not, see <http://www.gnu.org/licenses/>.
|
||||
-->
|
||||
|
||||
<?import javafx.scene.control.*?>
|
||||
<?import javafx.scene.layout.*?>
|
||||
<?import javafx.scene.control.Tab?>
|
||||
<?import javafx.scene.control.TabPane?>
|
||||
<?import javafx.scene.layout.AnchorPane?>
|
||||
<TabPane fx:id="root" fx:controller="io.bitsquare.gui.main.portfolio.PortfolioView"
|
||||
AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0"
|
||||
AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0" tabClosingPolicy="UNAVAILABLE"
|
||||
xmlns:fx="http://javafx.com/fxml">
|
||||
|
||||
<Tab fx:id="openOffersTab" text="Open offers"/>
|
||||
<Tab fx:id="openOffersTab" text="My offers"/>
|
||||
<Tab fx:id="pendingTradesTab" text="Open trades"/>
|
||||
<Tab fx:id="closedTradesTab" text="History"/>
|
||||
|
||||
|
@ -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() {
|
||||
|
@ -324,7 +324,7 @@ public class PendingTradesViewModel extends ActivatableWithDataModel<PendingTrad
|
||||
}
|
||||
|
||||
public String getSecurityDeposit() {
|
||||
return formatter.formatCoinWithCode(FeePolicy.SECURITY_DEPOSIT);
|
||||
return formatter.formatCoinWithCode(FeePolicy.getSecurityDeposit());
|
||||
}
|
||||
|
||||
public boolean isBlockChainMethod() {
|
||||
|
@ -116,7 +116,7 @@ public class CompletedView extends TradeStepDetailsView {
|
||||
});
|
||||
|
||||
if (BitsquareApp.DEV_MODE)
|
||||
withdrawAddressTextField.setText("mwajQdfYnve1knXnmv7JdeiVpeogTsck6S");
|
||||
withdrawAddressTextField.setText("mxAkWWaQBqwqcYstKzqLku3kzR6pbu2zHq");
|
||||
}
|
||||
|
||||
public void setBtcTradeAmountLabelText(String text) {
|
||||
|
@ -19,10 +19,12 @@ package io.bitsquare.gui.main.settings.preferences;
|
||||
|
||||
import io.bitsquare.gui.common.view.ActivatableViewAndModel;
|
||||
import io.bitsquare.gui.common.view.FxmlView;
|
||||
import io.bitsquare.gui.components.InputTextField;
|
||||
import io.bitsquare.gui.util.Layout;
|
||||
import io.bitsquare.locale.LanguageUtil;
|
||||
import io.bitsquare.locale.TradeCurrency;
|
||||
import io.bitsquare.user.BlockChainExplorer;
|
||||
import javafx.beans.value.ChangeListener;
|
||||
import javafx.scene.control.CheckBox;
|
||||
import javafx.scene.control.ComboBox;
|
||||
import javafx.scene.layout.GridPane;
|
||||
@ -44,6 +46,8 @@ public class PreferencesView extends ActivatableViewAndModel<GridPane, Preferenc
|
||||
private CheckBox useAnimationsCheckBox, useEffectsCheckBox, showPlaceOfferConfirmationCheckBox, showTakeOfferConfirmationCheckBox,
|
||||
autoSelectArbitratorsCheckBox;
|
||||
private int gridRow = 0;
|
||||
private InputTextField transactionFeeInputTextField;
|
||||
private ChangeListener<Boolean> transactionFeeFocusedListener;
|
||||
|
||||
@Inject
|
||||
public PreferencesView(PreferencesViewModel model) {
|
||||
@ -52,12 +56,16 @@ public class PreferencesView extends ActivatableViewAndModel<GridPane, Preferenc
|
||||
|
||||
@Override
|
||||
public void initialize() {
|
||||
addTitledGroupBg(root, gridRow, 3, "Preferences");
|
||||
addTitledGroupBg(root, gridRow, 4, "Preferences");
|
||||
tradeCurrencyComboBox = addLabelComboBox(root, gridRow, "Preferred currency:", Layout.FIRST_ROW_DISTANCE).second;
|
||||
languageComboBox = addLabelComboBox(root, ++gridRow, "Language:").second;
|
||||
// btcDenominationComboBox = addLabelComboBox(root, ++gridRow, "Bitcoin denomination:").second;
|
||||
blockExplorerComboBox = addLabelComboBox(root, ++gridRow, "Bitcoin block explorer:").second;
|
||||
|
||||
transactionFeeInputTextField = addLabelInputTextField(root, ++gridRow, "Transaction fee (satoshi/byte):").second;
|
||||
transactionFeeFocusedListener = (o, oldValue, newValue) -> {
|
||||
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<GridPane, Preferenc
|
||||
});
|
||||
blockExplorerComboBox.setOnAction(e -> 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<GridPane, Preferenc
|
||||
languageComboBox.setOnAction(null);
|
||||
tradeCurrencyComboBox.setOnAction(null);
|
||||
blockExplorerComboBox.setOnAction(null);
|
||||
transactionFeeInputTextField.textProperty().unbind();
|
||||
transactionFeeInputTextField.focusedProperty().removeListener(transactionFeeFocusedListener);
|
||||
useAnimationsCheckBox.setOnAction(null);
|
||||
useEffectsCheckBox.setOnAction(null);
|
||||
showPlaceOfferConfirmationCheckBox.setOnAction(null);
|
||||
|
@ -18,15 +18,20 @@
|
||||
package io.bitsquare.gui.main.settings.preferences;
|
||||
|
||||
import com.google.inject.Inject;
|
||||
import io.bitsquare.common.UserThread;
|
||||
import io.bitsquare.gui.common.model.ActivatableViewModel;
|
||||
import io.bitsquare.gui.popups.Popup;
|
||||
import io.bitsquare.locale.LanguageUtil;
|
||||
import io.bitsquare.locale.TradeCurrency;
|
||||
import io.bitsquare.user.BlockChainExplorer;
|
||||
import io.bitsquare.user.Preferences;
|
||||
import javafx.beans.property.SimpleStringProperty;
|
||||
import javafx.beans.property.StringProperty;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.collections.ObservableList;
|
||||
|
||||
import java.util.Locale;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
class PreferencesViewModel extends ActivatableViewModel {
|
||||
|
||||
@ -35,7 +40,8 @@ class PreferencesViewModel extends ActivatableViewModel {
|
||||
final ObservableList<BlockChainExplorer> blockExplorers;
|
||||
final ObservableList<TradeCurrency> tradeCurrencies;
|
||||
final ObservableList<String> 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();
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -99,7 +99,7 @@ public class EmptyWalletPopup extends Popup {
|
||||
Tuple2<Label, InputTextField> 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);
|
||||
|
@ -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}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user