mirror of
https://github.com/bisq-network/bisq.git
synced 2025-02-24 15:10:44 +01:00
Bugfix with withdrawal, display exact bitcoin value without rounding
This commit is contained in:
parent
a38fb9d21c
commit
24cc6800e7
10 changed files with 99 additions and 55 deletions
|
@ -24,7 +24,7 @@ public class Restrictions {
|
|||
|
||||
public static final Coin MIN_TRADE_AMOUNT = Coin.parseCoin("0.0001"); // 4 cent @ 400 EUR/BTC
|
||||
|
||||
public static boolean isAboveFixedTxFeeAndDust(Coin amount) {
|
||||
public static boolean isAboveFixedTxFeeForTradesAndDust(Coin amount) {
|
||||
return amount != null && amount.compareTo(FeePolicy.getFixedTxFeeForTrades().add(Transaction.MIN_NONDUST_OUTPUT)) > 0;
|
||||
}
|
||||
|
||||
|
|
|
@ -145,7 +145,7 @@ public class TradeWalletService {
|
|||
boolean useSavingsWallet, Coin tradingFee, String feeReceiverAddresses)
|
||||
throws InsufficientMoneyException, AddressFormatException {
|
||||
Transaction tradingFeeTx = new Transaction(params);
|
||||
Preconditions.checkArgument(Restrictions.isAboveFixedTxFeeAndDust(tradingFee),
|
||||
Preconditions.checkArgument(Restrictions.isAboveFixedTxFeeForTradesAndDust(tradingFee),
|
||||
"You cannot send an amount which are smaller than the fee + dust output.");
|
||||
Coin outPutAmount = tradingFee.subtract(FeePolicy.getFixedTxFeeForTrades());
|
||||
tradingFeeTx.addOutput(outPutAmount, new Address(params, feeReceiverAddresses));
|
||||
|
|
|
@ -717,7 +717,7 @@ public class WalletService {
|
|||
AddressEntryException, InsufficientMoneyException {
|
||||
Transaction tx = new Transaction(params);
|
||||
Preconditions.checkArgument(Restrictions.isAboveDust(amount),
|
||||
"You cannot send an amount which are smaller than 546 satoshis.");
|
||||
"The amount is too low (dust limit).");
|
||||
tx.addOutput(amount, new Address(params, toAddress));
|
||||
|
||||
Wallet.SendRequest sendRequest = Wallet.SendRequest.forTx(tx);
|
||||
|
@ -742,7 +742,7 @@ public class WalletService {
|
|||
AddressFormatException, AddressEntryException, InsufficientMoneyException {
|
||||
Transaction tx = new Transaction(params);
|
||||
Preconditions.checkArgument(Restrictions.isAboveDust(amount),
|
||||
"You cannot send an amount which are smaller than 546 satoshis.");
|
||||
"The amount is too low (dust limit).");
|
||||
tx.addOutput(amount, new Address(params, toAddress));
|
||||
|
||||
Wallet.SendRequest sendRequest = Wallet.SendRequest.forTx(tx);
|
||||
|
|
|
@ -28,21 +28,21 @@ public class RestrictionsTest {
|
|||
@Test
|
||||
public void testIsMinSpendableAmount() {
|
||||
Coin amount = null;
|
||||
assertFalse("tx unfunded, pending", Restrictions.isAboveFixedTxFeeAndDust(amount));
|
||||
assertFalse("tx unfunded, pending", Restrictions.isAboveFixedTxFeeForTradesAndDust(amount));
|
||||
|
||||
amount = Coin.ZERO;
|
||||
assertFalse("tx unfunded, pending", Restrictions.isAboveFixedTxFeeAndDust(amount));
|
||||
assertFalse("tx unfunded, pending", Restrictions.isAboveFixedTxFeeForTradesAndDust(amount));
|
||||
|
||||
amount = FeePolicy.getFixedTxFeeForTrades();
|
||||
assertFalse("tx unfunded, pending", Restrictions.isAboveFixedTxFeeAndDust(amount));
|
||||
assertFalse("tx unfunded, pending", Restrictions.isAboveFixedTxFeeForTradesAndDust(amount));
|
||||
|
||||
amount = Transaction.MIN_NONDUST_OUTPUT;
|
||||
assertFalse("tx unfunded, pending", Restrictions.isAboveFixedTxFeeAndDust(amount));
|
||||
assertFalse("tx unfunded, pending", Restrictions.isAboveFixedTxFeeForTradesAndDust(amount));
|
||||
|
||||
amount = FeePolicy.getFixedTxFeeForTrades().add(Transaction.MIN_NONDUST_OUTPUT);
|
||||
assertFalse("tx unfunded, pending", Restrictions.isAboveFixedTxFeeAndDust(amount));
|
||||
assertFalse("tx unfunded, pending", Restrictions.isAboveFixedTxFeeForTradesAndDust(amount));
|
||||
|
||||
amount = FeePolicy.getFixedTxFeeForTrades().add(Transaction.MIN_NONDUST_OUTPUT).add(Coin.valueOf(1));
|
||||
assertTrue("tx unfunded, pending", Restrictions.isAboveFixedTxFeeAndDust(amount));
|
||||
assertTrue("tx unfunded, pending", Restrictions.isAboveFixedTxFeeForTradesAndDust(amount));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -161,7 +161,7 @@ public class BitsquareApp extends Application {
|
|||
mainView.setPersistedFilesCorrupted(corruptedDatabaseFiles);
|
||||
});*/
|
||||
|
||||
scene = new Scene(mainView.getRoot(), 1150, 740);
|
||||
scene = new Scene(mainView.getRoot(), 1190, 740);
|
||||
scene.getStylesheets().setAll(
|
||||
"/io/bitsquare/gui/bitsquare.css",
|
||||
"/io/bitsquare/gui/images.css");
|
||||
|
@ -193,7 +193,7 @@ public class BitsquareApp extends Application {
|
|||
// configure the primary stage
|
||||
primaryStage.setTitle(env.getRequiredProperty(APP_NAME_KEY));
|
||||
primaryStage.setScene(scene);
|
||||
primaryStage.setMinWidth(1130);
|
||||
primaryStage.setMinWidth(1170);
|
||||
primaryStage.setMinHeight(620);
|
||||
|
||||
// on windows the title icon is also used as task bar icon in a larger size
|
||||
|
|
|
@ -152,7 +152,7 @@ public class MainView extends InitializableView<StackPane, MainViewModel> {
|
|||
return type != null ? "Market price (" + type.name + ")" : "";
|
||||
},
|
||||
model.marketPriceCurrency, model.typeProperty));
|
||||
HBox.setMargin(marketPriceBox.third, new Insets(0, 20, 0, 0));
|
||||
HBox.setMargin(marketPriceBox.third, new Insets(0, 0, 0, 0));
|
||||
|
||||
|
||||
Tuple2<TextField, VBox> availableBalanceBox = getBalanceBox("Available balance");
|
||||
|
@ -243,7 +243,7 @@ public class MainView extends InitializableView<StackPane, MainViewModel> {
|
|||
private Tuple2<TextField, VBox> getBalanceBox(String text) {
|
||||
TextField textField = new TextField();
|
||||
textField.setEditable(false);
|
||||
textField.setPrefWidth(120);
|
||||
textField.setPrefWidth(140);
|
||||
textField.setMouseTransparent(true);
|
||||
textField.setFocusTraversable(false);
|
||||
textField.setStyle("-fx-alignment: center; -fx-background-color: white;");
|
||||
|
|
|
@ -292,7 +292,7 @@ public class DepositView extends ActivatableView<VBox, Void> {
|
|||
|
||||
private Coin getAmountAsCoin() {
|
||||
Coin senderAmount = formatter.parseToCoin(amountTextField.getText());
|
||||
if (!Restrictions.isAboveFixedTxFeeAndDust(senderAmount)) {
|
||||
if (!Restrictions.isAboveFixedTxFeeForTradesAndDust(senderAmount)) {
|
||||
senderAmount = Coin.ZERO;
|
||||
/* new Popup()
|
||||
.warning("The amount is lower than the transaction fee and the min. possible tx value (dust).")
|
||||
|
|
|
@ -22,7 +22,6 @@ import de.jensd.fx.fontawesome.AwesomeIcon;
|
|||
import io.bitsquare.app.BitsquareApp;
|
||||
import io.bitsquare.btc.AddressEntry;
|
||||
import io.bitsquare.btc.AddressEntryException;
|
||||
import io.bitsquare.btc.Restrictions;
|
||||
import io.bitsquare.btc.WalletService;
|
||||
import io.bitsquare.btc.listeners.BalanceListener;
|
||||
import io.bitsquare.common.UserThread;
|
||||
|
@ -40,8 +39,10 @@ import io.bitsquare.trade.TradeManager;
|
|||
import io.bitsquare.trade.closed.ClosedTradableManager;
|
||||
import io.bitsquare.trade.failed.FailedTradesManager;
|
||||
import io.bitsquare.user.Preferences;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
import javafx.beans.property.ReadOnlyObjectWrapper;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
import javafx.beans.value.ChangeListener;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.collections.transformation.SortedList;
|
||||
|
@ -50,10 +51,7 @@ import javafx.scene.control.*;
|
|||
import javafx.scene.layout.VBox;
|
||||
import javafx.util.Callback;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.bitcoinj.core.AddressFormatException;
|
||||
import org.bitcoinj.core.Coin;
|
||||
import org.bitcoinj.core.InsufficientMoneyException;
|
||||
import org.bitcoinj.core.Transaction;
|
||||
import org.bitcoinj.core.*;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.spongycastle.crypto.params.KeyParameter;
|
||||
|
||||
|
@ -87,7 +85,10 @@ public class WithdrawalView extends ActivatableView<VBox, Void> {
|
|||
private Set<WithdrawalListItem> selectedItems = new HashSet<>();
|
||||
private BalanceListener balanceListener;
|
||||
private Set<String> fromAddresses;
|
||||
private Coin amountOfSelectedItems;
|
||||
private Coin amountOfSelectedItems = Coin.ZERO;
|
||||
private ObjectProperty<Coin> senderAmountAsCoinProperty = new SimpleObjectProperty<>(Coin.ZERO);
|
||||
private ChangeListener<String> amountListener;
|
||||
private ChangeListener<Boolean> amountFocusListener;
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
@ -131,6 +132,23 @@ public class WithdrawalView extends ActivatableView<VBox, Void> {
|
|||
updateList();
|
||||
}
|
||||
};
|
||||
amountListener = (observable, oldValue, newValue) -> {
|
||||
if (amountTextField.focusedProperty().get()) {
|
||||
try {
|
||||
senderAmountAsCoinProperty.set(formatter.parseToCoin(amountTextField.getText()));
|
||||
} catch (Throwable t) {
|
||||
log.error("Error at amountTextField input. " + t.toString());
|
||||
}
|
||||
}
|
||||
};
|
||||
amountFocusListener = (observable, oldValue, newValue) -> {
|
||||
if (oldValue && !newValue) {
|
||||
if (senderAmountAsCoinProperty.get().isPositive())
|
||||
amountTextField.setText(formatter.formatCoin(senderAmountAsCoinProperty.get()));
|
||||
else
|
||||
amountTextField.setText("");
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -141,17 +159,18 @@ public class WithdrawalView extends ActivatableView<VBox, Void> {
|
|||
|
||||
reset();
|
||||
|
||||
amountTextField.textProperty().addListener(amountListener);
|
||||
amountTextField.focusedProperty().addListener(amountFocusListener);
|
||||
walletService.addBalanceListener(balanceListener);
|
||||
withdrawButton.disableProperty().bind(Bindings.createBooleanBinding(() -> !areInputsValid(),
|
||||
amountTextField.textProperty(), withdrawToTextField.textProperty()));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void deactivate() {
|
||||
sortedList.comparatorProperty().unbind();
|
||||
observableList.forEach(WithdrawalListItem::cleanup);
|
||||
withdrawButton.disableProperty().unbind();
|
||||
walletService.removeBalanceListener(balanceListener);
|
||||
amountTextField.textProperty().removeListener(amountListener);
|
||||
amountTextField.focusedProperty().removeListener(amountFocusListener);
|
||||
}
|
||||
|
||||
|
||||
|
@ -161,8 +180,7 @@ public class WithdrawalView extends ActivatableView<VBox, Void> {
|
|||
|
||||
@FXML
|
||||
public void onWithdraw() {
|
||||
Coin senderAmount = formatter.parseToCoin(amountTextField.getText());
|
||||
if (Restrictions.isAboveFixedTxFeeAndDust(senderAmount)) {
|
||||
if (areInputsValid()) {
|
||||
FutureCallback<Transaction> callback = new FutureCallback<Transaction>() {
|
||||
@Override
|
||||
public void onSuccess(@javax.annotation.Nullable Transaction transaction) {
|
||||
|
@ -192,12 +210,13 @@ public class WithdrawalView extends ActivatableView<VBox, Void> {
|
|||
// TODO Get a proper fee calculation from BitcoinJ directly
|
||||
Coin requiredFee = walletService.getRequiredFeeForMultipleAddresses(fromAddresses,
|
||||
withdrawToTextField.getText(), amountOfSelectedItems);
|
||||
Coin receiverAmount = senderAmount.subtract(requiredFee);
|
||||
Coin receiverAmount = senderAmountAsCoinProperty.get().subtract(requiredFee);
|
||||
if (receiverAmount.isPositive()) {
|
||||
if (BitsquareApp.DEV_MODE) {
|
||||
doWithdraw(receiverAmount, callback);
|
||||
} else {
|
||||
new Popup().headLine("Confirm withdrawal request")
|
||||
.confirmation("Sending: " + formatter.formatCoinWithCode(senderAmount) + "\n" +
|
||||
.confirmation("Sending: " + formatter.formatCoinWithCode(senderAmountAsCoinProperty.get()) + "\n" +
|
||||
"From address: " + withdrawFromTextField.getText() + "\n" +
|
||||
"To receiving address: " + withdrawToTextField.getText() + ".\n" +
|
||||
"Required transaction fee is: " + formatter.formatCoinWithCode(requiredFee) + "\n\n" +
|
||||
|
@ -209,14 +228,15 @@ public class WithdrawalView extends ActivatableView<VBox, Void> {
|
|||
.show();
|
||||
|
||||
}
|
||||
} else {
|
||||
new Popup().warning("The amount you would like to send is too low as the bitcoin transaction fee will be deducted.\n" +
|
||||
"Please use a higher amount.").show();
|
||||
}
|
||||
} catch (Throwable e) {
|
||||
e.printStackTrace();
|
||||
log.error(e.getMessage());
|
||||
new Popup().error(e.getMessage()).show();
|
||||
new Popup().warning(e.getMessage()).show();
|
||||
}
|
||||
} else {
|
||||
new Popup().warning("The amount to transfer is lower than the transaction fee and the min. possible tx value (dust).")
|
||||
.show();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -233,8 +253,11 @@ public class WithdrawalView extends ActivatableView<VBox, Void> {
|
|||
if (!selectedItems.isEmpty()) {
|
||||
amountOfSelectedItems = Coin.valueOf(selectedItems.stream().mapToLong(e -> e.getBalance().getValue()).sum());
|
||||
if (amountOfSelectedItems.isPositive()) {
|
||||
senderAmountAsCoinProperty.set(amountOfSelectedItems);
|
||||
amountTextField.setText(formatter.formatCoin(amountOfSelectedItems));
|
||||
} else {
|
||||
senderAmountAsCoinProperty.set(Coin.ZERO);
|
||||
amountOfSelectedItems = Coin.ZERO;
|
||||
amountTextField.setText("");
|
||||
withdrawFromTextField.setText("");
|
||||
}
|
||||
|
@ -302,11 +325,17 @@ public class WithdrawalView extends ActivatableView<VBox, Void> {
|
|||
updateList();
|
||||
} catch (AddressFormatException e) {
|
||||
new Popup().warning("The address is not correct. Please check the address format.").show();
|
||||
} catch (Wallet.DustySendRequested e) {
|
||||
new Popup().warning("The amount you would like to send is below the dust limit and would be rejected by the bitcoin network.\n" +
|
||||
"Please use a higher amount.").show();
|
||||
} catch (AddressEntryException e) {
|
||||
new Popup().error(e.getMessage()).show();
|
||||
} catch (InsufficientMoneyException e) {
|
||||
log.warn(e.getMessage());
|
||||
new Popup().warning("You don't have enough fund in your wallet.").show();
|
||||
} catch (Throwable e) {
|
||||
log.warn(e.getMessage());
|
||||
new Popup().warning(e.getMessage()).show();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -319,6 +348,8 @@ public class WithdrawalView extends ActivatableView<VBox, Void> {
|
|||
withdrawFromTextField.setPromptText("Select a source address from the table");
|
||||
withdrawFromTextField.setTooltip(null);
|
||||
|
||||
amountOfSelectedItems = Coin.ZERO;
|
||||
senderAmountAsCoinProperty.set(Coin.ZERO);
|
||||
amountTextField.setText("");
|
||||
amountTextField.setPromptText("Set the amount to withdraw");
|
||||
|
||||
|
@ -342,14 +373,27 @@ public class WithdrawalView extends ActivatableView<VBox, Void> {
|
|||
}
|
||||
|
||||
private boolean areInputsValid() {
|
||||
if (amountTextField.getText().length() > 0) {
|
||||
Coin amount = formatter.parseToCoin(amountTextField.getText());
|
||||
return btcAddressValidator.validate(withdrawToTextField.getText()).isValid &&
|
||||
amount.compareTo(amountOfSelectedItems) <= 0 &&
|
||||
Restrictions.isAboveFixedTxFeeAndDust(amount);
|
||||
} else {
|
||||
if (!senderAmountAsCoinProperty.get().isPositive()) {
|
||||
new Popup().warning("Please fill in a valid value for the amount to send (max. 8 decimal places).").show();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!btcAddressValidator.validate(withdrawToTextField.getText()).isValid) {
|
||||
new Popup().warning("Please fill in a valid receiver bitcoin address.").show();
|
||||
return false;
|
||||
}
|
||||
if (!amountOfSelectedItems.isPositive()) {
|
||||
new Popup().warning("You need to select a source address in the table above.").show();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (senderAmountAsCoinProperty.get().compareTo(amountOfSelectedItems) > 0) {
|
||||
new Popup().warning("Your amount exceeds the available amount for the selected address.\n" +
|
||||
"Consider to select multiple addresses in the table above if you want to withdraw more.").show();
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -183,7 +183,7 @@ public class BuyerStep5View extends TradeStepView {
|
|||
} else {
|
||||
if (toAddresses.isEmpty()) {
|
||||
validateWithdrawAddress();
|
||||
} else if (Restrictions.isAboveFixedTxFeeAndDust(senderAmount)) {
|
||||
} else if (Restrictions.isAboveFixedTxFeeForTradesAndDust(senderAmount)) {
|
||||
|
||||
if (BitsquareApp.DEV_MODE) {
|
||||
doWithdrawal(receiverAmount);
|
||||
|
|
|
@ -53,7 +53,7 @@ public class BSFormatter {
|
|||
// Input of a group separator (1,123,45) lead to an validation error.
|
||||
// Note: BtcFormat was intended to be used, but it lead to many problems (automatic format to mBit,
|
||||
// no way to remove grouping separator). It seems to be not optimal for user input formatting.
|
||||
private MonetaryFormat coinFormat = MonetaryFormat.BTC.repeatOptionalDecimals(2, 2);
|
||||
private MonetaryFormat coinFormat = MonetaryFormat.BTC.minDecimals(2).repeatOptionalDecimals(1, 6);
|
||||
|
||||
// private String currencyCode = CurrencyUtil.getDefaultFiatCurrencyAsCode();
|
||||
|
||||
|
@ -97,7 +97,7 @@ public class BSFormatter {
|
|||
if (useMilliBit)
|
||||
return MonetaryFormat.MBTC;
|
||||
else
|
||||
return MonetaryFormat.BTC.repeatOptionalDecimals(2, 2);
|
||||
return MonetaryFormat.BTC.minDecimals(2).repeatOptionalDecimals(1, 6);
|
||||
}
|
||||
|
||||
/* public void setFiatCurrencyCode(String currencyCode) {
|
||||
|
|
Loading…
Add table
Reference in a new issue