mirror of
https://github.com/haveno-dex/haveno.git
synced 2025-06-25 07:10:48 -04:00
Add wallet restore to pw popup
This commit is contained in:
parent
a823c0360e
commit
97c54eb58e
3 changed files with 245 additions and 37 deletions
|
@ -396,7 +396,7 @@ public class MainView extends InitializableView<StackPane, MainViewModel> {
|
||||||
model.p2PNetworkIconId.addListener(splashP2PNetworkIconIdListener);
|
model.p2PNetworkIconId.addListener(splashP2PNetworkIconIdListener);
|
||||||
|
|
||||||
splashP2PNetworkProgressListener = (ov, oldValue, newValue) -> {
|
splashP2PNetworkProgressListener = (ov, oldValue, newValue) -> {
|
||||||
if ((double) newValue >= 1) {
|
if ((double) newValue != -1) {
|
||||||
splashP2PNetworkIndicator.setVisible(false);
|
splashP2PNetworkIndicator.setVisible(false);
|
||||||
splashP2PNetworkIndicator.setManaged(false);
|
splashP2PNetworkIndicator.setManaged(false);
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,36 +17,71 @@
|
||||||
|
|
||||||
package io.bitsquare.gui.main.overlays.windows;
|
package io.bitsquare.gui.main.overlays.windows;
|
||||||
|
|
||||||
|
import com.google.common.base.Splitter;
|
||||||
|
import io.bitsquare.app.BitsquareApp;
|
||||||
import io.bitsquare.btc.WalletService;
|
import io.bitsquare.btc.WalletService;
|
||||||
import io.bitsquare.common.UserThread;
|
import io.bitsquare.common.UserThread;
|
||||||
|
import io.bitsquare.common.util.Tuple2;
|
||||||
import io.bitsquare.crypto.ScryptUtil;
|
import io.bitsquare.crypto.ScryptUtil;
|
||||||
import io.bitsquare.gui.components.PasswordTextField;
|
import io.bitsquare.gui.components.PasswordTextField;
|
||||||
import io.bitsquare.gui.main.overlays.Overlay;
|
import io.bitsquare.gui.main.overlays.Overlay;
|
||||||
import io.bitsquare.gui.main.overlays.popups.Popup;
|
import io.bitsquare.gui.main.overlays.popups.Popup;
|
||||||
import io.bitsquare.gui.util.Transitions;
|
import io.bitsquare.gui.util.Transitions;
|
||||||
import io.bitsquare.gui.util.validation.PasswordValidator;
|
import io.bitsquare.gui.util.validation.PasswordValidator;
|
||||||
|
import io.bitsquare.locale.BSResources;
|
||||||
|
import javafx.beans.property.BooleanProperty;
|
||||||
|
import javafx.beans.property.SimpleBooleanProperty;
|
||||||
import javafx.beans.value.ChangeListener;
|
import javafx.beans.value.ChangeListener;
|
||||||
|
import javafx.geometry.HPos;
|
||||||
import javafx.geometry.Insets;
|
import javafx.geometry.Insets;
|
||||||
import javafx.scene.control.Button;
|
import javafx.geometry.Orientation;
|
||||||
import javafx.scene.control.Label;
|
import javafx.scene.Scene;
|
||||||
|
import javafx.scene.control.*;
|
||||||
|
import javafx.scene.input.KeyCode;
|
||||||
|
import javafx.scene.layout.ColumnConstraints;
|
||||||
import javafx.scene.layout.GridPane;
|
import javafx.scene.layout.GridPane;
|
||||||
import javafx.scene.layout.HBox;
|
import javafx.scene.layout.HBox;
|
||||||
|
import javafx.scene.layout.Priority;
|
||||||
import org.bitcoinj.core.Wallet;
|
import org.bitcoinj.core.Wallet;
|
||||||
import org.bitcoinj.crypto.KeyCrypterScrypt;
|
import org.bitcoinj.crypto.KeyCrypterScrypt;
|
||||||
|
import org.bitcoinj.crypto.MnemonicCode;
|
||||||
|
import org.bitcoinj.crypto.MnemonicException;
|
||||||
|
import org.bitcoinj.wallet.DeterministicSeed;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
import org.spongycastle.crypto.params.KeyParameter;
|
import org.spongycastle.crypto.params.KeyParameter;
|
||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.time.Instant;
|
||||||
|
import java.time.LocalDate;
|
||||||
|
import java.time.ZoneId;
|
||||||
|
import java.time.ZoneOffset;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
import static com.google.inject.internal.util.$Preconditions.checkArgument;
|
||||||
|
import static io.bitsquare.gui.util.FormBuilder.*;
|
||||||
|
import static javafx.beans.binding.Bindings.createBooleanBinding;
|
||||||
|
|
||||||
public class WalletPasswordWindow extends Overlay<WalletPasswordWindow> {
|
public class WalletPasswordWindow extends Overlay<WalletPasswordWindow> {
|
||||||
private static final Logger log = LoggerFactory.getLogger(WalletPasswordWindow.class);
|
private static final Logger log = LoggerFactory.getLogger(WalletPasswordWindow.class);
|
||||||
private final WalletService walletService;
|
private final WalletService walletService;
|
||||||
private Button unlockButton;
|
private Button unlockButton;
|
||||||
private AesKeyHandler aesKeyHandler;
|
private AesKeyHandler aesKeyHandler;
|
||||||
private PasswordTextField passwordTextField;
|
private PasswordTextField passwordTextField;
|
||||||
|
private Button forgotPasswordButton;
|
||||||
|
private Button restoreButton;
|
||||||
|
private TextArea restoreSeedWordsTextArea;
|
||||||
|
private DatePicker restoreDatePicker;
|
||||||
|
private SimpleBooleanProperty seedWordsValid = new SimpleBooleanProperty(false);
|
||||||
|
private SimpleBooleanProperty dateValid = new SimpleBooleanProperty(false);
|
||||||
|
private BooleanProperty seedWordsEdited = new SimpleBooleanProperty();
|
||||||
private ChangeListener<String> changeListener;
|
private ChangeListener<String> changeListener;
|
||||||
|
private ChangeListener<String> seedWordsTextAreaChangeListener;
|
||||||
|
private ChangeListener<Boolean> datePickerChangeListener;
|
||||||
|
private ChangeListener<Boolean> seedWordsValidChangeListener;
|
||||||
|
private ChangeListener<LocalDate> dateChangeListener;
|
||||||
|
private LocalDate walletCreationDate;
|
||||||
|
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
@ -57,23 +92,19 @@ public class WalletPasswordWindow extends Overlay<WalletPasswordWindow> {
|
||||||
void onAesKey(KeyParameter aesKey);
|
void onAesKey(KeyParameter aesKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
public WalletPasswordWindow(WalletService walletService) {
|
||||||
|
this.walletService = walletService;
|
||||||
|
type = Type.Attention;
|
||||||
|
width = 800;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
// Public API
|
// Public API
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void cleanup() {
|
|
||||||
if (passwordTextField != null)
|
|
||||||
passwordTextField.textProperty().addListener(changeListener);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Inject
|
|
||||||
public WalletPasswordWindow(WalletService walletService) {
|
|
||||||
this.walletService = walletService;
|
|
||||||
type = Type.Attention;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void show() {
|
public void show() {
|
||||||
if (gridPane != null) {
|
if (gridPane != null) {
|
||||||
rowIndex = -1;
|
rowIndex = -1;
|
||||||
|
@ -97,11 +128,42 @@ public class WalletPasswordWindow extends Overlay<WalletPasswordWindow> {
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void cleanup() {
|
||||||
|
if (passwordTextField != null)
|
||||||
|
passwordTextField.textProperty().removeListener(changeListener);
|
||||||
|
|
||||||
|
if (seedWordsValidChangeListener != null) {
|
||||||
|
seedWordsValid.removeListener(seedWordsValidChangeListener);
|
||||||
|
dateValid.removeListener(datePickerChangeListener);
|
||||||
|
restoreSeedWordsTextArea.textProperty().removeListener(seedWordsTextAreaChangeListener);
|
||||||
|
restoreDatePicker.valueProperty().removeListener(dateChangeListener);
|
||||||
|
restoreButton.disableProperty().unbind();
|
||||||
|
restoreButton.setOnAction(null);
|
||||||
|
restoreSeedWordsTextArea.setText("");
|
||||||
|
restoreDatePicker.setValue(null);
|
||||||
|
restoreSeedWordsTextArea.getStyleClass().remove("validation_error");
|
||||||
|
restoreDatePicker.getStyleClass().remove("validation_error");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
// Protected
|
// Protected
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void setupKeyHandler(Scene scene) {
|
||||||
|
if (!hideCloseButton) {
|
||||||
|
scene.setOnKeyPressed(e -> {
|
||||||
|
if (e.getCode() == KeyCode.ESCAPE) {
|
||||||
|
e.consume();
|
||||||
|
doClose();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void addInputFields() {
|
private void addInputFields() {
|
||||||
Label label = new Label("Enter password:");
|
Label label = new Label("Enter password:");
|
||||||
label.setWrapText(true);
|
label.setWrapText(true);
|
||||||
|
@ -113,9 +175,7 @@ public class WalletPasswordWindow extends Overlay<WalletPasswordWindow> {
|
||||||
GridPane.setRowIndex(passwordTextField, rowIndex);
|
GridPane.setRowIndex(passwordTextField, rowIndex);
|
||||||
GridPane.setColumnIndex(passwordTextField, 1);
|
GridPane.setColumnIndex(passwordTextField, 1);
|
||||||
PasswordValidator passwordValidator = new PasswordValidator();
|
PasswordValidator passwordValidator = new PasswordValidator();
|
||||||
changeListener = (observable, oldValue, newValue) -> {
|
changeListener = (observable, oldValue, newValue) -> unlockButton.setDisable(!passwordValidator.validate(newValue).isValid);
|
||||||
unlockButton.setDisable(!passwordValidator.validate(newValue).isValid);
|
|
||||||
};
|
|
||||||
passwordTextField.textProperty().addListener(changeListener);
|
passwordTextField.textProperty().addListener(changeListener);
|
||||||
gridPane.getChildren().addAll(label, passwordTextField);
|
gridPane.getChildren().addAll(label, passwordTextField);
|
||||||
}
|
}
|
||||||
|
@ -124,27 +184,9 @@ public class WalletPasswordWindow extends Overlay<WalletPasswordWindow> {
|
||||||
unlockButton = new Button("Unlock");
|
unlockButton = new Button("Unlock");
|
||||||
unlockButton.setDefaultButton(true);
|
unlockButton.setDefaultButton(true);
|
||||||
unlockButton.setDisable(true);
|
unlockButton.setDisable(true);
|
||||||
unlockButton.setOnAction(e -> checkPassword());
|
unlockButton.setOnAction(e -> {
|
||||||
|
|
||||||
Button cancelButton = new Button("Cancel");
|
|
||||||
cancelButton.setOnAction(event -> {
|
|
||||||
hide();
|
|
||||||
closeHandlerOptional.ifPresent(closeHandler -> closeHandler.run());
|
|
||||||
});
|
|
||||||
|
|
||||||
HBox hBox = new HBox();
|
|
||||||
hBox.setSpacing(10);
|
|
||||||
GridPane.setRowIndex(hBox, ++rowIndex);
|
|
||||||
GridPane.setColumnIndex(hBox, 1);
|
|
||||||
if (hideCloseButton)
|
|
||||||
hBox.getChildren().add(unlockButton);
|
|
||||||
else
|
|
||||||
hBox.getChildren().addAll(unlockButton, cancelButton);
|
|
||||||
gridPane.getChildren().add(hBox);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void checkPassword() {
|
|
||||||
String password = passwordTextField.getText();
|
String password = passwordTextField.getText();
|
||||||
|
checkArgument(password.length() < 50, "Password must be less then 50 characters.");
|
||||||
Wallet wallet = walletService.getWallet();
|
Wallet wallet = walletService.getWallet();
|
||||||
KeyCrypterScrypt keyCrypterScrypt = (KeyCrypterScrypt) wallet.getKeyCrypter();
|
KeyCrypterScrypt keyCrypterScrypt = (KeyCrypterScrypt) wallet.getKeyCrypter();
|
||||||
if (keyCrypterScrypt != null) {
|
if (keyCrypterScrypt != null) {
|
||||||
|
@ -164,5 +206,170 @@ public class WalletPasswordWindow extends Overlay<WalletPasswordWindow> {
|
||||||
} else {
|
} else {
|
||||||
log.error("wallet.getKeyCrypter() is null, than must not happen.");
|
log.error("wallet.getKeyCrypter() is null, than must not happen.");
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
forgotPasswordButton = new Button("Forgot password?");
|
||||||
|
forgotPasswordButton.setOnAction(e -> {
|
||||||
|
forgotPasswordButton.setDisable(true);
|
||||||
|
unlockButton.setDefaultButton(false);
|
||||||
|
showRestoreScreen();
|
||||||
|
});
|
||||||
|
|
||||||
|
Button cancelButton = new Button("Cancel");
|
||||||
|
cancelButton.setOnAction(event -> {
|
||||||
|
hide();
|
||||||
|
closeHandlerOptional.ifPresent(closeHandler -> closeHandler.run());
|
||||||
|
});
|
||||||
|
|
||||||
|
HBox hBox = new HBox();
|
||||||
|
hBox.setSpacing(10);
|
||||||
|
GridPane.setRowIndex(hBox, ++rowIndex);
|
||||||
|
GridPane.setColumnIndex(hBox, 1);
|
||||||
|
if (hideCloseButton)
|
||||||
|
hBox.getChildren().addAll(unlockButton, forgotPasswordButton);
|
||||||
|
else
|
||||||
|
hBox.getChildren().addAll(unlockButton, cancelButton);
|
||||||
|
gridPane.getChildren().add(hBox);
|
||||||
|
|
||||||
|
|
||||||
|
ColumnConstraints columnConstraints1 = new ColumnConstraints();
|
||||||
|
columnConstraints1.setHalignment(HPos.RIGHT);
|
||||||
|
columnConstraints1.setHgrow(Priority.SOMETIMES);
|
||||||
|
ColumnConstraints columnConstraints2 = new ColumnConstraints();
|
||||||
|
columnConstraints2.setHgrow(Priority.ALWAYS);
|
||||||
|
gridPane.getColumnConstraints().addAll(columnConstraints1, columnConstraints2);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void showRestoreScreen() {
|
||||||
|
Label headLine2Label = new Label(BSResources.get("Restore wallet from seed words"));
|
||||||
|
headLine2Label.setId("popup-headline");
|
||||||
|
headLine2Label.setMouseTransparent(true);
|
||||||
|
GridPane.setHalignment(headLine2Label, HPos.LEFT);
|
||||||
|
GridPane.setRowIndex(headLine2Label, ++rowIndex);
|
||||||
|
GridPane.setColumnSpan(headLine2Label, 2);
|
||||||
|
GridPane.setMargin(headLine2Label, new Insets(30, 0, 0, 0));
|
||||||
|
gridPane.getChildren().add(headLine2Label);
|
||||||
|
|
||||||
|
Separator separator = new Separator();
|
||||||
|
separator.setMouseTransparent(true);
|
||||||
|
separator.setOrientation(Orientation.HORIZONTAL);
|
||||||
|
separator.setStyle("-fx-background: #ccc;");
|
||||||
|
GridPane.setHalignment(separator, HPos.CENTER);
|
||||||
|
GridPane.setRowIndex(separator, ++rowIndex);
|
||||||
|
GridPane.setColumnSpan(separator, 2);
|
||||||
|
|
||||||
|
gridPane.getChildren().add(separator);
|
||||||
|
|
||||||
|
Tuple2<Label, TextArea> tuple = addLabelTextArea(gridPane, ++rowIndex, "Wallet seed words:", "", 5);
|
||||||
|
restoreSeedWordsTextArea = tuple.second;
|
||||||
|
restoreSeedWordsTextArea.setPrefHeight(60);
|
||||||
|
restoreSeedWordsTextArea.setStyle("-fx-border-color: #ddd;");
|
||||||
|
Tuple2<Label, DatePicker> labelDatePickerTuple2 = addLabelDatePicker(gridPane, ++rowIndex, "Creation Date:");
|
||||||
|
restoreDatePicker = labelDatePickerTuple2.second;
|
||||||
|
restoreButton = addButton(gridPane, ++rowIndex, "Restore wallet");
|
||||||
|
restoreButton.setDefaultButton(true);
|
||||||
|
stage.setHeight(340);
|
||||||
|
|
||||||
|
DeterministicSeed keyChainSeed = walletService.getWallet().getKeyChainSeed();
|
||||||
|
// wallet creation date is not encrypted
|
||||||
|
walletCreationDate = Instant.ofEpochSecond(keyChainSeed.getCreationTimeSeconds()).atZone(ZoneId.systemDefault()).toLocalDate();
|
||||||
|
|
||||||
|
|
||||||
|
restoreButton.disableProperty().bind(createBooleanBinding(() -> !seedWordsValid.get() || !dateValid.get() || !seedWordsEdited.get(),
|
||||||
|
seedWordsValid, dateValid, seedWordsEdited));
|
||||||
|
|
||||||
|
|
||||||
|
seedWordsValidChangeListener = (observable, oldValue, newValue) -> {
|
||||||
|
if (newValue) {
|
||||||
|
restoreSeedWordsTextArea.getStyleClass().remove("validation_error");
|
||||||
|
} else {
|
||||||
|
restoreSeedWordsTextArea.getStyleClass().add("validation_error");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
seedWordsTextAreaChangeListener = (observable, oldValue, newValue) -> {
|
||||||
|
seedWordsEdited.set(true);
|
||||||
|
try {
|
||||||
|
MnemonicCode codec = new MnemonicCode();
|
||||||
|
codec.check(Splitter.on(" ").splitToList(newValue));
|
||||||
|
seedWordsValid.set(true);
|
||||||
|
} catch (IOException | MnemonicException e) {
|
||||||
|
seedWordsValid.set(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
datePickerChangeListener = (observable, oldValue, newValue) -> {
|
||||||
|
if (newValue)
|
||||||
|
restoreDatePicker.getStyleClass().remove("validation_error");
|
||||||
|
else
|
||||||
|
restoreDatePicker.getStyleClass().add("validation_error");
|
||||||
|
};
|
||||||
|
dateChangeListener = (observable, oldValue, newValue) -> {
|
||||||
|
dateValid.set(walletCreationDate.equals(newValue));
|
||||||
|
};
|
||||||
|
|
||||||
|
seedWordsValid.addListener(seedWordsValidChangeListener);
|
||||||
|
dateValid.addListener(datePickerChangeListener);
|
||||||
|
restoreSeedWordsTextArea.textProperty().addListener(seedWordsTextAreaChangeListener);
|
||||||
|
restoreDatePicker.valueProperty().addListener(dateChangeListener);
|
||||||
|
restoreButton.disableProperty().bind(createBooleanBinding(() -> !seedWordsValid.get() || !dateValid.get() || !seedWordsEdited.get(),
|
||||||
|
seedWordsValid, dateValid, seedWordsEdited));
|
||||||
|
|
||||||
|
restoreButton.setOnAction(e -> onRestore());
|
||||||
|
|
||||||
|
restoreSeedWordsTextArea.getStyleClass().remove("validation_error");
|
||||||
|
restoreDatePicker.getStyleClass().remove("validation_error");
|
||||||
|
|
||||||
|
layout();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onRestore() {
|
||||||
|
Wallet wallet = walletService.getWallet();
|
||||||
|
if (wallet.getBalance(Wallet.BalanceType.AVAILABLE).value > 0) {
|
||||||
|
new Popup()
|
||||||
|
.warning("Your bitcoin wallet is not empty.\n\n" +
|
||||||
|
"You must empty this wallet before attempting to restore an older one, as mixing wallets " +
|
||||||
|
"together can lead to invalidated backups.\n\n" +
|
||||||
|
"Please finalize your trades, close all your open offers and go to the Funds section to withdraw your bitcoin.\n" +
|
||||||
|
"In case you cannot access your bitcoin you can use the emergency tool to empty the wallet.\n" +
|
||||||
|
"To open that emergency tool press \"cmd + e\".")
|
||||||
|
.show();
|
||||||
|
} else if (wallet.isEncrypted()) {
|
||||||
|
new Popup()
|
||||||
|
.warning("Your bitcoin wallet is encrypted.\n\n" +
|
||||||
|
"After restore, the wallet will no longer be encrypted and you must set a new password.\n\n" +
|
||||||
|
"Do you want to proceed?")
|
||||||
|
.closeButtonText("No")
|
||||||
|
.actionButtonText("Yes")
|
||||||
|
.onAction(this::doRestore)
|
||||||
|
.show();
|
||||||
|
} else {
|
||||||
|
doRestore();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void doRestore() {
|
||||||
|
log.info("Attempting wallet restore using seed '{}' from date {}", restoreSeedWordsTextArea.getText(), restoreDatePicker.getValue());
|
||||||
|
long date = restoreDatePicker.getValue().atStartOfDay().toEpochSecond(ZoneOffset.UTC);
|
||||||
|
DeterministicSeed seed = new DeterministicSeed(Splitter.on(" ").splitToList(restoreSeedWordsTextArea.getText()), null, "", date);
|
||||||
|
walletService.restoreSeedWords(seed,
|
||||||
|
() -> UserThread.execute(() -> {
|
||||||
|
log.debug("Wallet restored with seed words");
|
||||||
|
|
||||||
|
new Popup()
|
||||||
|
.feedback("Wallet restored successfully with the new seed words.\n\n" +
|
||||||
|
"You need to shut down and restart the application.")
|
||||||
|
.closeButtonText("Shut down")
|
||||||
|
.onClose(BitsquareApp.shutDownHandler::run)
|
||||||
|
.show();
|
||||||
|
}),
|
||||||
|
throwable -> UserThread.execute(() -> {
|
||||||
|
log.error(throwable.getMessage());
|
||||||
|
new Popup()
|
||||||
|
.error("An error occurred when restoring the wallet with seed words.\n" +
|
||||||
|
"Error message: " + throwable.getMessage())
|
||||||
|
.show();
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,6 +26,7 @@ validation.fiat.toLarge=Input larger as maximum possible amount is not allowed.
|
||||||
validation.btc.toSmall=Input results in a bitcoin value with a fraction of the smallest unit (Satoshi).
|
validation.btc.toSmall=Input results in a bitcoin value with a fraction of the smallest unit (Satoshi).
|
||||||
validation.btc.toLarge=Input larger as maximum trading amount of {0} is not allowed.
|
validation.btc.toLarge=Input larger as maximum trading amount of {0} is not allowed.
|
||||||
validation.passwordTooShort=The password you entered is too short. It needs to have min. 8 characters.
|
validation.passwordTooShort=The password you entered is too short. It needs to have min. 8 characters.
|
||||||
|
validation.passwordTooLong=The password you entered is too long. It cannot be longer as 50 characters.
|
||||||
|
|
||||||
# Create offer
|
# Create offer
|
||||||
createOffer.amount.prompt=Enter amount in BTC
|
createOffer.amount.prompt=Enter amount in BTC
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue