mirror of
https://github.com/haveno-dex/haveno.git
synced 2025-07-22 22:51:02 -04:00
Fix fee calculation, add adjustable non-trade mining fee
This commit is contained in:
parent
2abc2cd5bc
commit
565c44d94c
12 changed files with 230 additions and 144 deletions
|
@ -50,14 +50,14 @@ public class FeePolicy {
|
||||||
// The BitcoinJ fee calculation use kb so a tx size < 1kb will still pay the fee for a kb tx.
|
// 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
|
// 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
|
// 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(20_000); // 0.0002 BTC about 0.08 EUR @ 400 EUR/BTC
|
private static Coin NON_TRADE_FEE_PER_KB = Coin.valueOf(10_000); // 0.0001 BTC about 0.04 EUR @ 400 EUR/BTC
|
||||||
|
|
||||||
public static void setFeePerKb(Coin feePerKb) {
|
public static void setNonTradeFeePerKb(Coin nonTradeFeePerKb) {
|
||||||
FEE_PER_KB = feePerKb;
|
NON_TRADE_FEE_PER_KB = nonTradeFeePerKb;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Coin getFeePerKb() {
|
public static Coin getNonTradeFeePerKb() {
|
||||||
return FEE_PER_KB;
|
return NON_TRADE_FEE_PER_KB;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Some wallets don't support manual fees. Most use at least 0.0001 BTC (0.04 EUR @ 400 EUR/BTC)
|
// Some wallets don't support manual fees. Most use at least 0.0001 BTC (0.04 EUR @ 400 EUR/BTC)
|
||||||
|
|
|
@ -38,7 +38,10 @@ import org.bitcoinj.kits.WalletAppKit;
|
||||||
import org.bitcoinj.params.MainNetParams;
|
import org.bitcoinj.params.MainNetParams;
|
||||||
import org.bitcoinj.params.RegTestParams;
|
import org.bitcoinj.params.RegTestParams;
|
||||||
import org.bitcoinj.params.TestNet3Params;
|
import org.bitcoinj.params.TestNet3Params;
|
||||||
|
import org.bitcoinj.script.Script;
|
||||||
import org.bitcoinj.utils.Threading;
|
import org.bitcoinj.utils.Threading;
|
||||||
|
import org.bitcoinj.wallet.CoinSelection;
|
||||||
|
import org.bitcoinj.wallet.CoinSelector;
|
||||||
import org.bitcoinj.wallet.DeterministicSeed;
|
import org.bitcoinj.wallet.DeterministicSeed;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
|
@ -347,6 +350,10 @@ public class WalletService {
|
||||||
return addressEntryList.addAddressEntry(new AddressEntry(wallet.freshReceiveKey(), wallet.getParams(), context));
|
return addressEntryList.addAddressEntry(new AddressEntry(wallet.freshReceiveKey(), wallet.getParams(), context));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public AddressEntry createAddressEntry(AddressEntry.Context context) {
|
||||||
|
return addressEntryList.addAddressEntry(new AddressEntry(wallet.freshReceiveKey(), wallet.getParams(), context));
|
||||||
|
}
|
||||||
|
|
||||||
public Optional<AddressEntry> findAddressEntry(String address, AddressEntry.Context context) {
|
public Optional<AddressEntry> findAddressEntry(String address, AddressEntry.Context context) {
|
||||||
return getAddressEntryListAsImmutableList().stream()
|
return getAddressEntryListAsImmutableList().stream()
|
||||||
.filter(e -> address.equals(e.getAddressString()))
|
.filter(e -> address.equals(e.getAddressString()))
|
||||||
|
@ -521,45 +528,156 @@ public class WalletService {
|
||||||
|
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
// Withdrawal
|
// Withdrawal Fee calculation
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
public Coin getRequiredFee(String fromAddress,
|
public Coin getRequiredFee(String fromAddress,
|
||||||
String toAddress,
|
String toAddress,
|
||||||
Coin amount,
|
Coin amount,
|
||||||
@Nullable KeyParameter aesKey,
|
|
||||||
AddressEntry.Context context) throws AddressFormatException, AddressEntryException {
|
AddressEntry.Context context) throws AddressFormatException, AddressEntryException {
|
||||||
Coin fee;
|
Optional<AddressEntry> addressEntry = findAddressEntry(fromAddress, context);
|
||||||
try {
|
if (!addressEntry.isPresent())
|
||||||
wallet.completeTx(getSendRequest(fromAddress, toAddress, amount, aesKey, context));
|
throw new AddressEntryException("WithdrawFromAddress is not found in our wallet.");
|
||||||
// We use the min fee for now as the mix of savingswallet/trade wallet has some nasty edge cases...
|
|
||||||
fee = FeePolicy.getFixedTxFeeForTrades();
|
checkNotNull(addressEntry.get().getAddress(), "addressEntry.get().getAddress() must nto be null");
|
||||||
} catch (InsufficientMoneyException e) {
|
CoinSelector selector = new TradeWalletCoinSelector(params, addressEntry.get().getAddress());
|
||||||
log.info("The amount to be transferred is not enough to pay the transaction fees of {}. " +
|
return getFee(toAddress,
|
||||||
"We subtract that fee from the receivers amount to make the transaction possible.");
|
amount,
|
||||||
fee = e.missing;
|
selector);
|
||||||
}
|
|
||||||
return fee;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public Coin getRequiredFeeForMultipleAddresses(Set<String> fromAddresses,
|
public Coin getRequiredFeeForMultipleAddresses(Set<String> fromAddresses,
|
||||||
String toAddress,
|
String toAddress,
|
||||||
Coin amount,
|
Coin amount) throws AddressFormatException,
|
||||||
@Nullable KeyParameter aesKey) throws AddressFormatException,
|
|
||||||
AddressEntryException {
|
AddressEntryException {
|
||||||
Coin fee;
|
Set<AddressEntry> addressEntries = fromAddresses.stream()
|
||||||
try {
|
.map(address -> {
|
||||||
wallet.completeTx(getSendRequestForMultipleAddresses(fromAddresses, toAddress, amount, null, aesKey));
|
Optional<AddressEntry> addressEntryOptional = findAddressEntry(address, AddressEntry.Context.AVAILABLE);
|
||||||
// We use the min fee for now as the mix of savingswallet/trade wallet has some nasty edge cases...
|
if (!addressEntryOptional.isPresent())
|
||||||
fee = FeePolicy.getFixedTxFeeForTrades();
|
addressEntryOptional = findAddressEntry(address, AddressEntry.Context.OFFER_FUNDING);
|
||||||
} catch (InsufficientMoneyException e) {
|
if (!addressEntryOptional.isPresent())
|
||||||
log.info("The amount to be transferred is not enough to pay the transaction fees of {}. " +
|
addressEntryOptional = findAddressEntry(address, AddressEntry.Context.TRADE_PAYOUT);
|
||||||
"We subtract that fee from the receivers amount to make the transaction possible.");
|
if (!addressEntryOptional.isPresent())
|
||||||
fee = e.missing;
|
addressEntryOptional = findAddressEntry(address, AddressEntry.Context.ARBITRATOR);
|
||||||
|
return addressEntryOptional;
|
||||||
|
})
|
||||||
|
.filter(Optional::isPresent)
|
||||||
|
.map(Optional::get)
|
||||||
|
.collect(Collectors.toSet());
|
||||||
|
if (addressEntries.isEmpty())
|
||||||
|
throw new AddressEntryException("No Addresses for withdraw found in our wallet");
|
||||||
|
|
||||||
|
CoinSelector selector = new MultiAddressesCoinSelector(params, addressEntries);
|
||||||
|
return getFee(toAddress,
|
||||||
|
amount,
|
||||||
|
selector);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Coin getFee(String toAddress,
|
||||||
|
Coin amount,
|
||||||
|
CoinSelector selector) throws AddressFormatException, AddressEntryException {
|
||||||
|
List<TransactionOutput> candidates = wallet.calculateAllSpendCandidates();
|
||||||
|
CoinSelection bestCoinSelection = selector.select(params.getMaxMoney(), candidates);
|
||||||
|
Transaction tx = new Transaction(params);
|
||||||
|
tx.addOutput(amount, new Address(params, toAddress));
|
||||||
|
if (!adjustOutputDownwardsForFee(tx, bestCoinSelection, Coin.ZERO, FeePolicy.getNonTradeFeePerKb()))
|
||||||
|
throw new Wallet.CouldNotAdjustDownwards();
|
||||||
|
|
||||||
|
Coin fee = amount.subtract(tx.getOutput(0).getValue());
|
||||||
|
log.info("Required fee " + fee);
|
||||||
return fee;
|
return fee;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private boolean adjustOutputDownwardsForFee(Transaction tx, CoinSelection coinSelection, Coin baseFee, Coin feePerKb) {
|
||||||
|
TransactionOutput output = tx.getOutput(0);
|
||||||
|
// Check if we need additional fee due to the transaction's size
|
||||||
|
int size = tx.bitcoinSerialize().length;
|
||||||
|
size += estimateBytesForSigning(coinSelection);
|
||||||
|
Coin fee = baseFee.add(feePerKb.multiply((size / 1000) + 1));
|
||||||
|
output.setValue(output.getValue().subtract(fee));
|
||||||
|
// Check if we need additional fee due to the output's value
|
||||||
|
if (output.getValue().compareTo(Coin.CENT) < 0 && fee.compareTo(Transaction.REFERENCE_DEFAULT_MIN_TX_FEE) < 0)
|
||||||
|
output.setValue(output.getValue().subtract(Transaction.REFERENCE_DEFAULT_MIN_TX_FEE.subtract(fee)));
|
||||||
|
return output.getMinNonDustValue().compareTo(output.getValue()) <= 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private int estimateBytesForSigning(CoinSelection selection) {
|
||||||
|
int size = 0;
|
||||||
|
for (TransactionOutput output : selection.gathered) {
|
||||||
|
try {
|
||||||
|
Script script = output.getScriptPubKey();
|
||||||
|
ECKey key = null;
|
||||||
|
Script redeemScript = null;
|
||||||
|
if (script.isSentToAddress()) {
|
||||||
|
key = wallet.findKeyFromPubHash(script.getPubKeyHash());
|
||||||
|
checkNotNull(key, "Coin selection includes unspendable outputs");
|
||||||
|
} else if (script.isPayToScriptHash()) {
|
||||||
|
redeemScript = wallet.findRedeemDataFromScriptHash(script.getPubKeyHash()).redeemScript;
|
||||||
|
checkNotNull(redeemScript, "Coin selection includes unspendable outputs");
|
||||||
|
}
|
||||||
|
size += script.getNumberOfBytesRequiredToSpend(key, redeemScript);
|
||||||
|
} catch (ScriptException e) {
|
||||||
|
// If this happens it means an output script in a wallet tx could not be understood. That should never
|
||||||
|
// happen, if it does it means the wallet has got into an inconsistent state.
|
||||||
|
throw new IllegalStateException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return size;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// Withdrawal Send
|
||||||
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
public String sendFunds(String fromAddress,
|
||||||
|
String toAddress,
|
||||||
|
Coin receiverAmount,
|
||||||
|
@Nullable KeyParameter aesKey,
|
||||||
|
AddressEntry.Context context,
|
||||||
|
FutureCallback<Transaction> callback) throws AddressFormatException,
|
||||||
|
AddressEntryException, InsufficientMoneyException {
|
||||||
|
Wallet.SendResult sendResult = wallet.sendCoins(getSendRequest(fromAddress, toAddress, receiverAmount, aesKey, context));
|
||||||
|
Futures.addCallback(sendResult.broadcastComplete, callback);
|
||||||
|
|
||||||
|
printTxWithInputs("sendFunds", sendResult.tx);
|
||||||
|
return sendResult.tx.getHashAsString();
|
||||||
|
}
|
||||||
|
|
||||||
|
public String sendFundsForMultipleAddresses(Set<String> fromAddresses,
|
||||||
|
String toAddress,
|
||||||
|
Coin receiverAmount,
|
||||||
|
@Nullable String changeAddress,
|
||||||
|
@Nullable KeyParameter aesKey,
|
||||||
|
FutureCallback<Transaction> callback) throws AddressFormatException,
|
||||||
|
AddressEntryException, InsufficientMoneyException {
|
||||||
|
Wallet.SendResult sendResult = wallet.sendCoins(getSendRequestForMultipleAddresses(fromAddresses, toAddress,
|
||||||
|
receiverAmount, changeAddress, aesKey));
|
||||||
|
Futures.addCallback(sendResult.broadcastComplete, callback);
|
||||||
|
|
||||||
|
printTxWithInputs("sendFunds", sendResult.tx);
|
||||||
|
return sendResult.tx.getHashAsString();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void emptyWallet(String toAddress, KeyParameter aesKey, ResultHandler resultHandler, ErrorMessageHandler errorMessageHandler)
|
||||||
|
throws InsufficientMoneyException, AddressFormatException {
|
||||||
|
Wallet.SendRequest sendRequest = Wallet.SendRequest.emptyWallet(new Address(params, toAddress));
|
||||||
|
sendRequest.aesKey = aesKey;
|
||||||
|
Wallet.SendResult sendResult = wallet.sendCoins(sendRequest);
|
||||||
|
sendRequest.feePerKb = FeePolicy.getNonTradeFeePerKb();
|
||||||
|
Futures.addCallback(sendResult.broadcastComplete, new FutureCallback<Transaction>() {
|
||||||
|
@Override
|
||||||
|
public void onSuccess(Transaction result) {
|
||||||
|
resultHandler.handleResult();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFailure(@NotNull Throwable t) {
|
||||||
|
errorMessageHandler.handleErrorMessage(t.getMessage());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
private Wallet.SendRequest getSendRequest(String fromAddress,
|
private Wallet.SendRequest getSendRequest(String fromAddress,
|
||||||
String toAddress,
|
String toAddress,
|
||||||
Coin amount,
|
Coin amount,
|
||||||
|
@ -581,7 +699,7 @@ public class WalletService {
|
||||||
checkNotNull(addressEntry.get().getAddress(), "addressEntry.get().getAddress() must nto be null");
|
checkNotNull(addressEntry.get().getAddress(), "addressEntry.get().getAddress() must nto be null");
|
||||||
sendRequest.coinSelector = new TradeWalletCoinSelector(params, addressEntry.get().getAddress());
|
sendRequest.coinSelector = new TradeWalletCoinSelector(params, addressEntry.get().getAddress());
|
||||||
sendRequest.changeAddress = addressEntry.get().getAddress();
|
sendRequest.changeAddress = addressEntry.get().getAddress();
|
||||||
sendRequest.feePerKb = FeePolicy.getFeePerKb();
|
sendRequest.feePerKb = FeePolicy.getNonTradeFeePerKb();
|
||||||
return sendRequest;
|
return sendRequest;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -606,6 +724,8 @@ public class WalletService {
|
||||||
addressEntryOptional = findAddressEntry(address, AddressEntry.Context.OFFER_FUNDING);
|
addressEntryOptional = findAddressEntry(address, AddressEntry.Context.OFFER_FUNDING);
|
||||||
if (!addressEntryOptional.isPresent())
|
if (!addressEntryOptional.isPresent())
|
||||||
addressEntryOptional = findAddressEntry(address, AddressEntry.Context.TRADE_PAYOUT);
|
addressEntryOptional = findAddressEntry(address, AddressEntry.Context.TRADE_PAYOUT);
|
||||||
|
if (!addressEntryOptional.isPresent())
|
||||||
|
addressEntryOptional = findAddressEntry(address, AddressEntry.Context.ARBITRATOR);
|
||||||
return addressEntryOptional;
|
return addressEntryOptional;
|
||||||
})
|
})
|
||||||
.filter(Optional::isPresent)
|
.filter(Optional::isPresent)
|
||||||
|
@ -629,60 +749,10 @@ public class WalletService {
|
||||||
}
|
}
|
||||||
checkNotNull(changeAddressAddressEntry, "change address must not be null");
|
checkNotNull(changeAddressAddressEntry, "change address must not be null");
|
||||||
sendRequest.changeAddress = changeAddressAddressEntry.getAddress();
|
sendRequest.changeAddress = changeAddressAddressEntry.getAddress();
|
||||||
sendRequest.feePerKb = FeePolicy.getFeePerKb();
|
sendRequest.feePerKb = FeePolicy.getNonTradeFeePerKb();
|
||||||
return sendRequest;
|
return sendRequest;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String sendFunds(String fromAddress,
|
|
||||||
String toAddress,
|
|
||||||
Coin amount,
|
|
||||||
@Nullable KeyParameter aesKey,
|
|
||||||
AddressEntry.Context context,
|
|
||||||
FutureCallback<Transaction> callback) throws AddressFormatException,
|
|
||||||
AddressEntryException, InsufficientMoneyException {
|
|
||||||
Coin fee = getRequiredFee(fromAddress, toAddress, amount, aesKey, context);
|
|
||||||
Wallet.SendResult sendResult = wallet.sendCoins(getSendRequest(fromAddress, toAddress, amount.subtract(fee), aesKey, context));
|
|
||||||
Futures.addCallback(sendResult.broadcastComplete, callback);
|
|
||||||
|
|
||||||
printTxWithInputs("sendFunds", sendResult.tx);
|
|
||||||
return sendResult.tx.getHashAsString();
|
|
||||||
}
|
|
||||||
|
|
||||||
public String sendFundsForMultipleAddresses(Set<String> fromAddresses,
|
|
||||||
String toAddress,
|
|
||||||
Coin amount,
|
|
||||||
@Nullable String changeAddress,
|
|
||||||
@Nullable KeyParameter aesKey,
|
|
||||||
FutureCallback<Transaction> callback) throws AddressFormatException,
|
|
||||||
AddressEntryException, InsufficientMoneyException {
|
|
||||||
Coin fee = getRequiredFeeForMultipleAddresses(fromAddresses, toAddress, amount, aesKey);
|
|
||||||
Wallet.SendResult sendResult = wallet.sendCoins(getSendRequestForMultipleAddresses(fromAddresses, toAddress,
|
|
||||||
amount.subtract(fee), changeAddress, aesKey));
|
|
||||||
Futures.addCallback(sendResult.broadcastComplete, callback);
|
|
||||||
|
|
||||||
printTxWithInputs("sendFunds", sendResult.tx);
|
|
||||||
return sendResult.tx.getHashAsString();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void emptyWallet(String toAddress, KeyParameter aesKey, ResultHandler resultHandler, ErrorMessageHandler errorMessageHandler)
|
|
||||||
throws InsufficientMoneyException, AddressFormatException {
|
|
||||||
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) {
|
|
||||||
resultHandler.handleResult();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onFailure(@NotNull Throwable t) {
|
|
||||||
errorMessageHandler.handleErrorMessage(t.getMessage());
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
// Getters
|
// Getters
|
||||||
|
|
|
@ -306,7 +306,7 @@ public class TradeManager {
|
||||||
// Trade
|
// Trade
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
public void onWithdrawRequest(String toAddress, KeyParameter aesKey, Trade trade, ResultHandler resultHandler, FaultHandler faultHandler) {
|
public void onWithdrawRequest(String toAddress, Coin receiverAmount, KeyParameter aesKey, Trade trade, ResultHandler resultHandler, FaultHandler faultHandler) {
|
||||||
String fromAddress = walletService.getOrCreateAddressEntry(trade.getId(), AddressEntry.Context.TRADE_PAYOUT).getAddressString();
|
String fromAddress = walletService.getOrCreateAddressEntry(trade.getId(), AddressEntry.Context.TRADE_PAYOUT).getAddressString();
|
||||||
|
|
||||||
FutureCallback<Transaction> callback = new FutureCallback<Transaction>() {
|
FutureCallback<Transaction> callback = new FutureCallback<Transaction>() {
|
||||||
|
@ -328,7 +328,7 @@ public class TradeManager {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
try {
|
try {
|
||||||
walletService.sendFunds(fromAddress, toAddress, trade.getPayoutAmount(), aesKey, AddressEntry.Context.TRADE_PAYOUT, callback);
|
walletService.sendFunds(fromAddress, toAddress, receiverAmount, aesKey, AddressEntry.Context.TRADE_PAYOUT, callback);
|
||||||
} catch (AddressFormatException | InsufficientMoneyException | AddressEntryException e) {
|
} catch (AddressFormatException | InsufficientMoneyException | AddressEntryException e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
log.error(e.getMessage());
|
log.error(e.getMessage());
|
||||||
|
|
|
@ -105,7 +105,7 @@ public final class Preferences implements Persistable {
|
||||||
private boolean showOwnOffersInOfferBook;
|
private boolean showOwnOffersInOfferBook;
|
||||||
private Locale preferredLocale;
|
private Locale preferredLocale;
|
||||||
private TradeCurrency preferredTradeCurrency;
|
private TradeCurrency preferredTradeCurrency;
|
||||||
private long txFeePerKB = FeePolicy.getFeePerKb().value;
|
private long nonTradeTxFeePerKB = FeePolicy.getNonTradeFeePerKb().value;
|
||||||
private double maxPriceDistanceInPercent;
|
private double maxPriceDistanceInPercent;
|
||||||
|
|
||||||
// Observable wrappers
|
// Observable wrappers
|
||||||
|
@ -162,7 +162,7 @@ public final class Preferences implements Persistable {
|
||||||
maxPriceDistanceInPercent = 0.2;
|
maxPriceDistanceInPercent = 0.2;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
setTxFeePerKB(persisted.getTxFeePerKB());
|
setNonTradeTxFeePerKB(persisted.getNonTradeTxFeePerKB());
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
// leave default value
|
// leave default value
|
||||||
}
|
}
|
||||||
|
@ -306,15 +306,15 @@ public final class Preferences implements Persistable {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setTxFeePerKB(long txFeePerKB) throws Exception {
|
public void setNonTradeTxFeePerKB(long nonTradeTxFeePerKB) throws Exception {
|
||||||
if (txFeePerKB < Transaction.REFERENCE_DEFAULT_MIN_TX_FEE.value)
|
if (nonTradeTxFeePerKB < Transaction.REFERENCE_DEFAULT_MIN_TX_FEE.value)
|
||||||
throw new Exception("Transaction fee must be at least 5 satoshi/byte");
|
throw new Exception("Transaction fee must be at least 5 satoshi/byte");
|
||||||
|
|
||||||
if (txFeePerKB < Transaction.REFERENCE_DEFAULT_MIN_TX_FEE.value)
|
if (nonTradeTxFeePerKB > 500_000)
|
||||||
throw new Exception("Transaction fee must be at least 5 satoshi/byte");
|
throw new Exception("Transaction fee is in the range of 10-100 satoshi/byte. Your input is above any reasonable value (>500 satoshi/byte).");
|
||||||
|
|
||||||
this.txFeePerKB = txFeePerKB;
|
this.nonTradeTxFeePerKB = nonTradeTxFeePerKB;
|
||||||
FeePolicy.setFeePerKb(Coin.valueOf(txFeePerKB));
|
FeePolicy.setNonTradeFeePerKb(Coin.valueOf(nonTradeTxFeePerKB));
|
||||||
storage.queueUpForSave();
|
storage.queueUpForSave();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -435,8 +435,8 @@ public final class Preferences implements Persistable {
|
||||||
return preferredTradeCurrency;
|
return preferredTradeCurrency;
|
||||||
}
|
}
|
||||||
|
|
||||||
public long getTxFeePerKB() {
|
public long getNonTradeTxFeePerKB() {
|
||||||
return Math.max(Transaction.REFERENCE_DEFAULT_MIN_TX_FEE.value, txFeePerKB);
|
return Math.max(Transaction.REFERENCE_DEFAULT_MIN_TX_FEE.value, nonTradeTxFeePerKB);
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean getUseTorForBitcoinJ() {
|
public boolean getUseTorForBitcoinJ() {
|
||||||
|
|
|
@ -186,7 +186,7 @@ public class DepositView extends ActivatableView<VBox, Void> {
|
||||||
new Popup().warning("You have already at least one address which is not used yet in any transaction.\n" +
|
new Popup().warning("You have already at least one address which is not used yet in any transaction.\n" +
|
||||||
"Please select in the address table an unused address.").show();
|
"Please select in the address table an unused address.").show();
|
||||||
} else {
|
} else {
|
||||||
AddressEntry newSavingsAddressEntry = walletService.getOrCreateAddressEntry(AddressEntry.Context.AVAILABLE);
|
AddressEntry newSavingsAddressEntry = walletService.createAddressEntry(AddressEntry.Context.AVAILABLE);
|
||||||
updateList();
|
updateList();
|
||||||
observableList.stream()
|
observableList.stream()
|
||||||
.filter(depositListItem -> depositListItem.getAddressString().equals(newSavingsAddressEntry.getAddressString()))
|
.filter(depositListItem -> depositListItem.getAddressString().equals(newSavingsAddressEntry.getAddressString()))
|
||||||
|
|
|
@ -196,10 +196,10 @@ public class WithdrawalView extends ActivatableView<VBox, Void> {
|
||||||
};
|
};
|
||||||
try {
|
try {
|
||||||
Coin requiredFee = walletService.getRequiredFeeForMultipleAddresses(fromAddresses,
|
Coin requiredFee = walletService.getRequiredFeeForMultipleAddresses(fromAddresses,
|
||||||
withdrawToTextField.getText(), senderAmount, null);
|
withdrawToTextField.getText(), senderAmount);
|
||||||
Coin receiverAmount = senderAmount.subtract(requiredFee);
|
Coin receiverAmount = senderAmount.subtract(requiredFee);
|
||||||
if (BitsquareApp.DEV_MODE) {
|
if (BitsquareApp.DEV_MODE) {
|
||||||
doWithdraw(senderAmount, callback);
|
doWithdraw(receiverAmount, callback);
|
||||||
} else {
|
} else {
|
||||||
new Popup().headLine("Confirm withdrawal request")
|
new Popup().headLine("Confirm withdrawal request")
|
||||||
.confirmation("Sending: " + formatter.formatCoinWithCode(senderAmount) + "\n" +
|
.confirmation("Sending: " + formatter.formatCoinWithCode(senderAmount) + "\n" +
|
||||||
|
|
|
@ -408,7 +408,7 @@ class TakeOfferDataModel extends ActivatableDataModel {
|
||||||
//noinspection SimplifiableIfStatement
|
//noinspection SimplifiableIfStatement
|
||||||
if (amountAsCoin.get() != null && offer != null) {
|
if (amountAsCoin.get() != null && offer != null) {
|
||||||
Coin customAmount = offer.getAmount().subtract(amountAsCoin.get());
|
Coin customAmount = offer.getAmount().subtract(amountAsCoin.get());
|
||||||
Coin dustAndFee = FeePolicy.getFeePerKb().add(Transaction.MIN_NONDUST_OUTPUT);
|
Coin dustAndFee = FeePolicy.getFixedTxFeeForTrades().add(Transaction.MIN_NONDUST_OUTPUT);
|
||||||
return customAmount.isPositive() && customAmount.isLessThan(dustAndFee);
|
return customAmount.isPositive() && customAmount.isLessThan(dustAndFee);
|
||||||
} else {
|
} else {
|
||||||
return true;
|
return true;
|
||||||
|
|
|
@ -186,11 +186,13 @@ public abstract class Overlay<T extends Overlay> {
|
||||||
Scene rootScene = owner.getScene();
|
Scene rootScene = owner.getScene();
|
||||||
if (rootScene != null) {
|
if (rootScene != null) {
|
||||||
Window window = rootScene.getWindow();
|
Window window = rootScene.getWindow();
|
||||||
|
if (window != null && positionListener != null) {
|
||||||
window.xProperty().removeListener(positionListener);
|
window.xProperty().removeListener(positionListener);
|
||||||
window.yProperty().removeListener(positionListener);
|
window.yProperty().removeListener(positionListener);
|
||||||
window.widthProperty().removeListener(positionListener);
|
window.widthProperty().removeListener(positionListener);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public T onClose(Runnable closeHandler) {
|
public T onClose(Runnable closeHandler) {
|
||||||
this.closeHandlerOptional = Optional.of(closeHandler);
|
this.closeHandlerOptional = Optional.of(closeHandler);
|
||||||
|
|
|
@ -147,12 +147,12 @@ public class PendingTradesDataModel extends ActivatableDataModel {
|
||||||
((SellerTrade) getTrade()).onFiatPaymentReceived(resultHandler, errorMessageHandler);
|
((SellerTrade) getTrade()).onFiatPaymentReceived(resultHandler, errorMessageHandler);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void onWithdrawRequest(String toAddress, ResultHandler resultHandler, FaultHandler faultHandler) {
|
public void onWithdrawRequest(String toAddress, Coin receiverAmount, ResultHandler resultHandler, FaultHandler faultHandler) {
|
||||||
checkNotNull(getTrade(), "trade must not be null");
|
checkNotNull(getTrade(), "trade must not be null");
|
||||||
if (walletService.getWallet().isEncrypted()) {
|
if (walletService.getWallet().isEncrypted()) {
|
||||||
walletPasswordWindow.onAesKey(aesKey -> doWithdrawRequest(toAddress, aesKey, resultHandler, faultHandler)).show();
|
walletPasswordWindow.onAesKey(aesKey -> doWithdrawRequest(toAddress, receiverAmount, aesKey, resultHandler, faultHandler)).show();
|
||||||
} else
|
} else
|
||||||
doWithdrawRequest(toAddress, null, resultHandler, faultHandler);
|
doWithdrawRequest(toAddress, receiverAmount, null, resultHandler, faultHandler);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void onOpenDispute() {
|
public void onOpenDispute() {
|
||||||
|
@ -279,10 +279,11 @@ public class PendingTradesDataModel extends ActivatableDataModel {
|
||||||
selectedItemProperty.set(item);
|
selectedItemProperty.set(item);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void doWithdrawRequest(String toAddress, KeyParameter aesKey, ResultHandler resultHandler, FaultHandler faultHandler) {
|
private void doWithdrawRequest(String toAddress, Coin receiverAmount, KeyParameter aesKey, ResultHandler resultHandler, FaultHandler faultHandler) {
|
||||||
if (toAddress != null && toAddress.length() > 0) {
|
if (toAddress != null && toAddress.length() > 0) {
|
||||||
tradeManager.onWithdrawRequest(
|
tradeManager.onWithdrawRequest(
|
||||||
toAddress,
|
toAddress,
|
||||||
|
receiverAmount,
|
||||||
aesKey,
|
aesKey,
|
||||||
getTrade(),
|
getTrade(),
|
||||||
() -> {
|
() -> {
|
||||||
|
|
|
@ -168,6 +168,9 @@ public class BuyerStep5View extends TradeStepView {
|
||||||
// TODO at some error situation it can be tha the funds are already paid out and we get stuck here
|
// TODO at some error situation it can be tha the funds are already paid out and we get stuck here
|
||||||
// need handling to remove the trade (planned for next release)
|
// need handling to remove the trade (planned for next release)
|
||||||
Coin balance = walletService.getBalanceForAddress(fromAddressesEntry.getAddress());
|
Coin balance = walletService.getBalanceForAddress(fromAddressesEntry.getAddress());
|
||||||
|
try {
|
||||||
|
Coin requiredFee = walletService.getRequiredFee(fromAddresses, toAddresses, senderAmount, AddressEntry.Context.TRADE_PAYOUT);
|
||||||
|
Coin receiverAmount = senderAmount.subtract(requiredFee);
|
||||||
if (balance.isZero()) {
|
if (balance.isZero()) {
|
||||||
new Popup().warning("Your funds have already been withdrawn.\nPlease check the transaction history.").show();
|
new Popup().warning("Your funds have already been withdrawn.\nPlease check the transaction history.").show();
|
||||||
model.dataModel.tradeManager.addTradeToClosedTrades(trade);
|
model.dataModel.tradeManager.addTradeToClosedTrades(trade);
|
||||||
|
@ -175,12 +178,10 @@ public class BuyerStep5View extends TradeStepView {
|
||||||
if (toAddresses.isEmpty()) {
|
if (toAddresses.isEmpty()) {
|
||||||
validateWithdrawAddress();
|
validateWithdrawAddress();
|
||||||
} else if (Restrictions.isAboveFixedTxFeeAndDust(senderAmount)) {
|
} else if (Restrictions.isAboveFixedTxFeeAndDust(senderAmount)) {
|
||||||
try {
|
|
||||||
if (BitsquareApp.DEV_MODE) {
|
if (BitsquareApp.DEV_MODE) {
|
||||||
doWithdrawal();
|
doWithdrawal(receiverAmount);
|
||||||
} else {
|
} else {
|
||||||
Coin requiredFee = walletService.getRequiredFee(fromAddresses, toAddresses, senderAmount, null, AddressEntry.Context.TRADE_PAYOUT);
|
|
||||||
Coin receiverAmount = senderAmount.subtract(requiredFee);
|
|
||||||
BSFormatter formatter = model.formatter;
|
BSFormatter formatter = model.formatter;
|
||||||
String key = "reviewWithdrawalAtTradeComplete";
|
String key = "reviewWithdrawalAtTradeComplete";
|
||||||
if (!BitsquareApp.DEV_MODE && preferences.showAgain(key)) {
|
if (!BitsquareApp.DEV_MODE && preferences.showAgain(key)) {
|
||||||
|
@ -197,11 +198,18 @@ public class BuyerStep5View extends TradeStepView {
|
||||||
withdrawToExternalWalletButton.setDisable(false);
|
withdrawToExternalWalletButton.setDisable(false);
|
||||||
})
|
})
|
||||||
.actionButtonText("Yes")
|
.actionButtonText("Yes")
|
||||||
.onAction(() -> doWithdrawal())
|
.onAction(() -> doWithdrawal(receiverAmount))
|
||||||
.dontShowAgainId(key, preferences)
|
.dontShowAgainId(key, preferences)
|
||||||
.show();
|
.show();
|
||||||
} else {
|
} else {
|
||||||
doWithdrawal();
|
doWithdrawal(receiverAmount);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
new Popup()
|
||||||
|
.warning("The amount to transfer is lower than the transaction fee and the min. possible tx value (dust).")
|
||||||
|
.show();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (AddressFormatException e) {
|
} catch (AddressFormatException e) {
|
||||||
|
@ -209,22 +217,15 @@ public class BuyerStep5View extends TradeStepView {
|
||||||
} catch (AddressEntryException e) {
|
} catch (AddressEntryException e) {
|
||||||
log.error(e.getMessage());
|
log.error(e.getMessage());
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
new Popup()
|
|
||||||
.warning("The amount to transfer is lower than the transaction fee and the min. possible tx value (dust).")
|
|
||||||
.show();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void doWithdrawal() {
|
private void doWithdrawal(Coin receiverAmount) {
|
||||||
useSavingsWalletButton.setDisable(true);
|
useSavingsWalletButton.setDisable(true);
|
||||||
withdrawToExternalWalletButton.setDisable(true);
|
withdrawToExternalWalletButton.setDisable(true);
|
||||||
|
|
||||||
model.dataModel.onWithdrawRequest(withdrawAddressTextField.getText(),
|
model.dataModel.onWithdrawRequest(withdrawAddressTextField.getText(),
|
||||||
() -> {
|
receiverAmount,
|
||||||
handleTradeCompleted();
|
this::handleTradeCompleted,
|
||||||
},
|
|
||||||
(errorMessage, throwable) -> {
|
(errorMessage, throwable) -> {
|
||||||
useSavingsWalletButton.setDisable(false);
|
useSavingsWalletButton.setDisable(false);
|
||||||
withdrawToExternalWalletButton.setDisable(false);
|
withdrawToExternalWalletButton.setDisable(false);
|
||||||
|
|
|
@ -31,8 +31,6 @@ import io.bitsquare.gui.util.Layout;
|
||||||
import io.bitsquare.locale.*;
|
import io.bitsquare.locale.*;
|
||||||
import io.bitsquare.user.BlockChainExplorer;
|
import io.bitsquare.user.BlockChainExplorer;
|
||||||
import io.bitsquare.user.Preferences;
|
import io.bitsquare.user.Preferences;
|
||||||
import javafx.beans.property.SimpleStringProperty;
|
|
||||||
import javafx.beans.property.StringProperty;
|
|
||||||
import javafx.beans.value.ChangeListener;
|
import javafx.beans.value.ChangeListener;
|
||||||
import javafx.collections.FXCollections;
|
import javafx.collections.FXCollections;
|
||||||
import javafx.collections.ObservableList;
|
import javafx.collections.ObservableList;
|
||||||
|
@ -44,6 +42,7 @@ import javafx.scene.layout.AnchorPane;
|
||||||
import javafx.scene.layout.GridPane;
|
import javafx.scene.layout.GridPane;
|
||||||
import javafx.util.Callback;
|
import javafx.util.Callback;
|
||||||
import javafx.util.StringConverter;
|
import javafx.util.StringConverter;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
@ -61,7 +60,7 @@ public class PreferencesView extends ActivatableViewAndModel<GridPane, Activatab
|
||||||
|
|
||||||
private CheckBox useAnimationsCheckBox, autoSelectArbitratorsCheckBox, showOwnOffersInOfferBook;
|
private CheckBox useAnimationsCheckBox, autoSelectArbitratorsCheckBox, showOwnOffersInOfferBook;
|
||||||
private int gridRow = 0;
|
private int gridRow = 0;
|
||||||
//private InputTextField transactionFeeInputTextField;
|
private InputTextField transactionFeeInputTextField;
|
||||||
private ChangeListener<Boolean> transactionFeeFocusedListener;
|
private ChangeListener<Boolean> transactionFeeFocusedListener;
|
||||||
private final Preferences preferences;
|
private final Preferences preferences;
|
||||||
private BSFormatter formatter;
|
private BSFormatter formatter;
|
||||||
|
@ -75,7 +74,6 @@ public class PreferencesView extends ActivatableViewAndModel<GridPane, Activatab
|
||||||
final ObservableList<String> btcDenominations = FXCollections.observableArrayList(Preferences.getBtcDenominations());
|
final ObservableList<String> btcDenominations = FXCollections.observableArrayList(Preferences.getBtcDenominations());
|
||||||
final ObservableList<BlockChainExplorer> blockExplorers;
|
final ObservableList<BlockChainExplorer> blockExplorers;
|
||||||
final ObservableList<String> languageCodes;
|
final ObservableList<String> languageCodes;
|
||||||
final StringProperty transactionFeePerByte = new SimpleStringProperty();
|
|
||||||
public final ObservableList<FiatCurrency> fiatCurrencies;
|
public final ObservableList<FiatCurrency> fiatCurrencies;
|
||||||
public final ObservableList<FiatCurrency> allFiatCurrencies;
|
public final ObservableList<FiatCurrency> allFiatCurrencies;
|
||||||
public final ObservableList<CryptoCurrency> cryptoCurrencies;
|
public final ObservableList<CryptoCurrency> cryptoCurrencies;
|
||||||
|
@ -288,7 +286,7 @@ public class PreferencesView extends ActivatableViewAndModel<GridPane, Activatab
|
||||||
}
|
}
|
||||||
|
|
||||||
private void initializeOtherOptions() {
|
private void initializeOtherOptions() {
|
||||||
TitledGroupBg titledGroupBg = addTitledGroupBg(root, ++gridRow, 3, "General preferences", Layout.GROUP_DISTANCE);
|
TitledGroupBg titledGroupBg = addTitledGroupBg(root, ++gridRow, 4, "General preferences", Layout.GROUP_DISTANCE);
|
||||||
GridPane.setColumnSpan(titledGroupBg, 4);
|
GridPane.setColumnSpan(titledGroupBg, 4);
|
||||||
// userLanguageComboBox = addLabelComboBox(root, gridRow, "Language:", Layout.FIRST_ROW_AND_GROUP_DISTANCE).second;
|
// userLanguageComboBox = addLabelComboBox(root, gridRow, "Language:", Layout.FIRST_ROW_AND_GROUP_DISTANCE).second;
|
||||||
// btcDenominationComboBox = addLabelComboBox(root, ++gridRow, "Bitcoin denomination:").second;
|
// btcDenominationComboBox = addLabelComboBox(root, ++gridRow, "Bitcoin denomination:").second;
|
||||||
|
@ -313,11 +311,21 @@ public class PreferencesView extends ActivatableViewAndModel<GridPane, Activatab
|
||||||
UserThread.runAfter(() -> deviationInputTextField.setText(formatter.formatToPercent(preferences.getMaxPriceDistanceInPercent())), 100, TimeUnit.MILLISECONDS);
|
UserThread.runAfter(() -> deviationInputTextField.setText(formatter.formatToPercent(preferences.getMaxPriceDistanceInPercent())), 100, TimeUnit.MILLISECONDS);
|
||||||
};
|
};
|
||||||
|
|
||||||
// TODO need a bit extra work to separate trade and non trade tx fees before it can be used
|
transactionFeeInputTextField = addLabelInputTextField(root, ++gridRow, "Transaction fee (satoshi/byte):").second;
|
||||||
/*transactionFeeInputTextField = addLabelInputTextField(root, ++gridRow, "Transaction fee (satoshi/byte):").second;
|
|
||||||
transactionFeeFocusedListener = (o, oldValue, newValue) -> {
|
transactionFeeFocusedListener = (o, oldValue, newValue) -> {
|
||||||
onFocusOutTransactionFeeTextField(oldValue, newValue);
|
if (oldValue && !newValue) {
|
||||||
};*/
|
try {
|
||||||
|
int val = Integer.parseInt(transactionFeeInputTextField.getText());
|
||||||
|
preferences.setNonTradeTxFeePerKB(val * 1000);
|
||||||
|
} catch (NumberFormatException t) {
|
||||||
|
new Popup().warning("Please enter integer numbers only.").show();
|
||||||
|
transactionFeeInputTextField.setText(getNonTradeTxFeePerKB());
|
||||||
|
} catch (Throwable t) {
|
||||||
|
new Popup().warning("Your input was not accepted.\n" + t.getMessage()).show();
|
||||||
|
transactionFeeInputTextField.setText(getNonTradeTxFeePerKB());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private void initializeDisplayOptions() {
|
private void initializeDisplayOptions() {
|
||||||
|
@ -378,7 +386,7 @@ public class PreferencesView extends ActivatableViewAndModel<GridPane, Activatab
|
||||||
}
|
}
|
||||||
|
|
||||||
private void activateOtherOptions() {
|
private void activateOtherOptions() {
|
||||||
transactionFeePerByte.set(String.valueOf(preferences.getTxFeePerKB() / 1000));
|
transactionFeeInputTextField.setText(getNonTradeTxFeePerKB());
|
||||||
|
|
||||||
/* btcDenominationComboBox.setDisable(true);
|
/* btcDenominationComboBox.setDisable(true);
|
||||||
btcDenominationComboBox.setItems(btcDenominations);
|
btcDenominationComboBox.setItems(btcDenominations);
|
||||||
|
@ -423,8 +431,12 @@ public class PreferencesView extends ActivatableViewAndModel<GridPane, Activatab
|
||||||
deviationInputTextField.textProperty().addListener(deviationListener);
|
deviationInputTextField.textProperty().addListener(deviationListener);
|
||||||
deviationInputTextField.focusedProperty().addListener(deviationFocusedListener);
|
deviationInputTextField.focusedProperty().addListener(deviationFocusedListener);
|
||||||
|
|
||||||
// transactionFeeInputTextField.textProperty().bindBidirectional(transactionFeePerByte);
|
transactionFeeInputTextField.focusedProperty().addListener(transactionFeeFocusedListener);
|
||||||
// transactionFeeInputTextField.focusedProperty().addListener(transactionFeeFocusedListener);
|
}
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
private String getNonTradeTxFeePerKB() {
|
||||||
|
return String.valueOf(preferences.getNonTradeTxFeePerKB() / 1000);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void activateDisplayPreferences() {
|
private void activateDisplayPreferences() {
|
||||||
|
@ -454,8 +466,7 @@ public class PreferencesView extends ActivatableViewAndModel<GridPane, Activatab
|
||||||
blockChainExplorerComboBox.setOnAction(null);
|
blockChainExplorerComboBox.setOnAction(null);
|
||||||
deviationInputTextField.textProperty().removeListener(deviationListener);
|
deviationInputTextField.textProperty().removeListener(deviationListener);
|
||||||
deviationInputTextField.focusedProperty().removeListener(deviationFocusedListener);
|
deviationInputTextField.focusedProperty().removeListener(deviationFocusedListener);
|
||||||
// transactionFeeInputTextField.textProperty().unbind();
|
transactionFeeInputTextField.focusedProperty().removeListener(transactionFeeFocusedListener);
|
||||||
/// transactionFeeInputTextField.focusedProperty().removeListener(transactionFeeFocusedListener);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -85,6 +85,7 @@ public class PeerServiceTest {
|
||||||
SeedNode seedNode = new SeedNode(test_dummy_dir);
|
SeedNode seedNode = new SeedNode(test_dummy_dir);
|
||||||
seedNodes.add(seedNode);
|
seedNodes.add(seedNode);
|
||||||
seedNode.createAndStartP2PService(true);
|
seedNode.createAndStartP2PService(true);
|
||||||
|
|
||||||
seedNode.getSeedNodeP2PService().start(new P2PServiceListener() {
|
seedNode.getSeedNodeP2PService().start(new P2PServiceListener() {
|
||||||
@Override
|
@Override
|
||||||
public void onRequestingDataCompleted() {
|
public void onRequestingDataCompleted() {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue