mirror of
https://github.com/sparrowwallet/sparrow.git
synced 2026-07-31 03:56:16 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
be86b4feaa | ||
|
|
37763e9557 | ||
|
|
80c4f4f5f6 | ||
|
|
6c3fe93d1e | ||
|
|
76eff2de48 | ||
|
|
07a6818823 | ||
|
|
2253a1bb97 | ||
|
|
36ee8add08 | ||
|
|
883e75c0df | ||
|
|
cc908b09c7 | ||
|
|
ce963ed5b6 | ||
|
|
951e33dc06 | ||
|
|
6a6a6b1cca | ||
|
|
8953d404fa | ||
|
|
b366177782 | ||
|
|
d0c827c2c7 | ||
|
|
5c29bf51b7 | ||
|
|
d426703dcc | ||
|
|
78f0721168 | ||
|
|
20d3f07059 | ||
|
|
1140a678ad | ||
|
|
6e8d44bc8c | ||
|
|
ad3b384feb | ||
|
|
f38350b38d | ||
|
|
62060c9839 | ||
|
|
8975f6f666 | ||
|
|
c7351cd191 | ||
|
|
62b1dc3900 | ||
|
|
f37ff47850 | ||
|
|
cfaa1f6c6e | ||
|
|
91c94b94eb | ||
|
|
a5eb7da067 | ||
|
|
195dbcef3b | ||
|
|
24955604e2 | ||
|
|
0305afbc02 | ||
|
|
d4c3c3afa8 |
+4
-2
@@ -5,7 +5,7 @@ plugins {
|
||||
id 'org.gradlex.extra-java-module-info' version '1.9'
|
||||
}
|
||||
|
||||
def sparrowVersion = '2.1.0'
|
||||
def sparrowVersion = '2.1.3'
|
||||
def os = org.gradle.internal.os.OperatingSystem.current()
|
||||
def osName = os.getFamilyName()
|
||||
if(os.macOsX) {
|
||||
@@ -72,7 +72,7 @@ dependencies {
|
||||
implementation('org.jdbi:jdbi3-sqlobject:3.20.0') {
|
||||
exclude group: 'org.slf4j'
|
||||
}
|
||||
implementation('org.flywaydb:flyway-core:9.1.3')
|
||||
implementation('org.flywaydb:flyway-core:9.22.3')
|
||||
implementation('org.fxmisc.richtext:richtextfx:0.10.4')
|
||||
implementation('no.tornado:tornadofx-controls:1.0.4')
|
||||
implementation('com.google.zxing:javase:3.4.0') {
|
||||
@@ -189,6 +189,7 @@ application {
|
||||
"--add-opens=javafx.graphics/com.sun.javafx.tk=centerdevice.nsmenufx",
|
||||
"--add-opens=javafx.graphics/com.sun.javafx.tk.quantum=centerdevice.nsmenufx",
|
||||
"--add-opens=javafx.graphics/com.sun.glass.ui=centerdevice.nsmenufx",
|
||||
"--add-opens=javafx.graphics/com.sun.glass.ui.mac=centerdevice.nsmenufx",
|
||||
"--add-opens=javafx.controls/com.sun.javafx.scene.control=centerdevice.nsmenufx",
|
||||
"--add-opens=javafx.graphics/com.sun.javafx.menu=centerdevice.nsmenufx",
|
||||
"--add-opens=javafx.graphics/com.sun.glass.ui=com.sparrowwallet.sparrow",
|
||||
@@ -237,6 +238,7 @@ jlink {
|
||||
"--add-opens=javafx.graphics/com.sun.javafx.tk=centerdevice.nsmenufx",
|
||||
"--add-opens=javafx.graphics/com.sun.javafx.tk.quantum=centerdevice.nsmenufx",
|
||||
"--add-opens=javafx.graphics/com.sun.glass.ui=centerdevice.nsmenufx",
|
||||
"--add-opens=javafx.graphics/com.sun.glass.ui.mac=centerdevice.nsmenufx",
|
||||
"--add-opens=javafx.controls/com.sun.javafx.scene.control=centerdevice.nsmenufx",
|
||||
"--add-opens=javafx.graphics/com.sun.javafx.menu=centerdevice.nsmenufx",
|
||||
"--add-opens=javafx.graphics/com.sun.glass.ui=com.sparrowwallet.sparrow",
|
||||
|
||||
+1
-1
Submodule drongo updated: 342c85a39e...5fd8e9416a
+1
-1
Submodule lark updated: b379338cc2...b41e83a002
@@ -1,5 +1,5 @@
|
||||
Package: sparrow
|
||||
Version: 2.1.0-1
|
||||
Version: 2.1.3-1
|
||||
Section: utils
|
||||
Maintainer: Craig Raw <mail@sparrowwallet.com>
|
||||
Priority: optional
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
Package: sparrow
|
||||
Version: 2.1.0-1
|
||||
Version: 2.1.3-1
|
||||
Section: utils
|
||||
Maintainer: Craig Raw <mail@sparrowwallet.com>
|
||||
Priority: optional
|
||||
|
||||
@@ -31,7 +31,7 @@ case "$1" in
|
||||
if ! groups "${SUDO_USER:-$(whoami)}" | grep -q plugdev; then
|
||||
usermod -aG plugdev "${SUDO_USER:-$(whoami)}"
|
||||
fi
|
||||
if [ -x /bin/udevadm ]; then
|
||||
if [ -w /sys/devices ] && [ -w /sys/kernel/uevent_seqnum ] && [ -x /bin/udevadm ]; then
|
||||
/bin/udevadm control --reload
|
||||
/bin/udevadm trigger
|
||||
fi
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
Summary: Sparrow
|
||||
Name: sparrow
|
||||
Version: 2.1.0
|
||||
Version: 2.1.3
|
||||
Release: 1
|
||||
License: ASL 2.0
|
||||
Vendor: Unknown
|
||||
@@ -86,7 +86,7 @@ fi
|
||||
if ! groups "${SUDO_USER:-$(whoami)}" | grep -q plugdev; then
|
||||
usermod -aG plugdev "${SUDO_USER:-$(whoami)}"
|
||||
fi
|
||||
if [ -x /bin/udevadm ]; then
|
||||
if [ -w /sys/devices ] && [ -w /sys/kernel/uevent_seqnum ] && [ -x /bin/udevadm ]; then
|
||||
/bin/udevadm control --reload
|
||||
/bin/udevadm trigger
|
||||
fi
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>2.1.0</string>
|
||||
<string>2.1.3</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<!-- See https://developer.apple.com/app-store/categories/ for list of AppStore categories -->
|
||||
@@ -35,6 +35,8 @@
|
||||
<string>true</string>
|
||||
<key>NSCameraUsageDescription</key>
|
||||
<string>Sparrow requires access to the camera in order to scan QR codes</string>
|
||||
<key>NSLocalNetworkUsageDescription</key>
|
||||
<string>Sparrow requires access to the local network in order to connect to your configured server</string>
|
||||
<key>CFBundleURLTypes</key>
|
||||
<array>
|
||||
<dict>
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
package com.sparrowwallet.sparrow;
|
||||
|
||||
import com.beust.jcommander.JCommander;
|
||||
import com.google.common.base.Charsets;
|
||||
import com.google.common.eventbus.Subscribe;
|
||||
import com.google.common.io.ByteSource;
|
||||
import com.sparrowwallet.drongo.*;
|
||||
import com.sparrowwallet.drongo.crypto.*;
|
||||
import com.sparrowwallet.drongo.policy.PolicyType;
|
||||
@@ -633,19 +631,10 @@ public class AppController implements Initializable {
|
||||
byte[] bytes = Files.readAllBytes(file.toPath());
|
||||
String name = file.getName();
|
||||
|
||||
try {
|
||||
if(Utils.isHex(bytes) || Utils.isBase64(bytes)) {
|
||||
addTransactionTab(name, file, new String(bytes, StandardCharsets.UTF_8).trim());
|
||||
} else {
|
||||
addTransactionTab(name, file, bytes);
|
||||
} catch(ParseException e) {
|
||||
ByteArrayInputStream inputStream = new ByteArrayInputStream(bytes);
|
||||
ByteSource byteSource = new ByteSource() {
|
||||
@Override
|
||||
public InputStream openStream() {
|
||||
return inputStream;
|
||||
}
|
||||
};
|
||||
|
||||
String text = byteSource.asCharSource(Charsets.UTF_8).read().trim();
|
||||
addTransactionTab(name, file, text);
|
||||
}
|
||||
} catch(IOException e) {
|
||||
showErrorDialog("Error opening file", e.getMessage());
|
||||
@@ -1378,7 +1367,7 @@ public class AppController implements Initializable {
|
||||
public void exportWallet(ActionEvent event) {
|
||||
WalletForm selectedWalletForm = getSelectedWalletForm();
|
||||
if(selectedWalletForm != null) {
|
||||
WalletExportDialog dlg = new WalletExportDialog(selectedWalletForm);
|
||||
WalletExportDialog dlg = new WalletExportDialog(selectedWalletForm, getSelectedWalletForms());
|
||||
dlg.initOwner(rootStack.getScene().getWindow());
|
||||
Optional<Wallet> wallet = dlg.showAndWait();
|
||||
if(wallet.isPresent()) {
|
||||
@@ -1480,6 +1469,7 @@ public class AppController implements Initializable {
|
||||
stage.setAlwaysOnTop(true);
|
||||
stage.setAlwaysOnTop(false);
|
||||
if(event.getSource() instanceof File file) {
|
||||
downloadVerifierDialog.setInitialFile(file);
|
||||
downloadVerifierDialog.setSignatureFile(file);
|
||||
}
|
||||
return;
|
||||
|
||||
@@ -1123,7 +1123,8 @@ public class AppServices {
|
||||
public static boolean isWhirlpoolPostmixCompatible(Wallet wallet) {
|
||||
return WHIRLPOOL_NETWORKS.contains(Network.get())
|
||||
&& wallet.getScriptType() != ScriptType.P2TR //Taproot not yet supported
|
||||
&& wallet.getKeystores().size() == 1;
|
||||
&& wallet.getKeystores().size() == 1
|
||||
&& wallet.getKeystores().getFirst().getWalletModel() != WalletModel.BITBOX_02; //BitBox02 does not support high account numbers
|
||||
}
|
||||
|
||||
public static List<Wallet> addWhirlpoolWallets(Wallet decryptedWallet, String walletId, Storage storage) {
|
||||
|
||||
@@ -18,7 +18,7 @@ import java.util.*;
|
||||
public class SparrowWallet {
|
||||
public static final String APP_ID = "sparrow";
|
||||
public static final String APP_NAME = "Sparrow";
|
||||
public static final String APP_VERSION = "2.1.0";
|
||||
public static final String APP_VERSION = "2.1.3";
|
||||
public static final String APP_VERSION_SUFFIX = "";
|
||||
public static final String APP_HOME_PROPERTY = "sparrow.home";
|
||||
public static final String NETWORK_ENV_PROPERTY = "SPARROW_NETWORK";
|
||||
|
||||
@@ -62,6 +62,7 @@ public class DownloadVerifierDialog extends Dialog<ButtonBar.ButtonData> {
|
||||
private static final List<String> ARCHIVE_EXTENSIONS = List.of("zip", "tar.gz", "tar.bz2", "tar.xz", "rar", "7z");
|
||||
|
||||
private static final String SPARROW_RELEASE_PREFIX = "sparrow-";
|
||||
private static final String SPARROW_RELEASE_ALT_PREFIX = "sparrow_";
|
||||
private static final String SPARROW_SIGNATURE_SUFFIX = "-manifest.txt.asc";
|
||||
private static final Pattern SPARROW_RELEASE_VERSION = Pattern.compile("[0-9]+(\\.[0-9]+)*");
|
||||
private static final long MIN_VALID_SPARROW_RELEASE_SIZE = 10 * 1024 * 1024;
|
||||
@@ -70,6 +71,7 @@ public class DownloadVerifierDialog extends Dialog<ButtonBar.ButtonData> {
|
||||
private final ObjectProperty<File> manifest = new SimpleObjectProperty<>();
|
||||
private final ObjectProperty<File> publicKey = new SimpleObjectProperty<>();
|
||||
private final ObjectProperty<File> release = new SimpleObjectProperty<>();
|
||||
private final ObjectProperty<File> initial = new SimpleObjectProperty<>();
|
||||
|
||||
private final BooleanProperty manifestDisabled = new SimpleBooleanProperty();
|
||||
private final BooleanProperty publicKeyDisabled = new SimpleBooleanProperty();
|
||||
@@ -81,7 +83,7 @@ public class DownloadVerifierDialog extends Dialog<ButtonBar.ButtonData> {
|
||||
|
||||
private static File lastFileParent;
|
||||
|
||||
public DownloadVerifierDialog(File initialSignatureFile) {
|
||||
public DownloadVerifierDialog(File initialFile) {
|
||||
final DialogPane dialogPane = getDialogPane();
|
||||
dialogPane.getStylesheets().add(AppServices.class.getResource("general.css").toExternalForm());
|
||||
dialogPane.getStylesheets().add(AppServices.class.getResource("dialog.css").toExternalForm());
|
||||
@@ -223,11 +225,17 @@ public class DownloadVerifierDialog extends Dialog<ButtonBar.ButtonData> {
|
||||
});
|
||||
|
||||
release.addListener((observable, oldValue, releaseFile) -> {
|
||||
if(releaseFile != null) {
|
||||
initial.set(null);
|
||||
}
|
||||
verify();
|
||||
});
|
||||
|
||||
if(initialSignatureFile != null) {
|
||||
javafx.application.Platform.runLater(() -> signature.set(initialSignatureFile));
|
||||
if(initialFile != null) {
|
||||
javafx.application.Platform.runLater(() -> {
|
||||
initial.set(initialFile);
|
||||
signature.set(initialFile);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -455,7 +463,8 @@ public class DownloadVerifierDialog extends Dialog<ButtonBar.ButtonData> {
|
||||
}
|
||||
}
|
||||
|
||||
if(providedFile.getName().toLowerCase(Locale.ROOT).startsWith(SPARROW_RELEASE_PREFIX)) {
|
||||
String providedName = providedFile.getName().toLowerCase(Locale.ROOT);
|
||||
if(providedName.startsWith(SPARROW_RELEASE_PREFIX) || providedName.startsWith(SPARROW_RELEASE_ALT_PREFIX)) {
|
||||
Matcher matcher = SPARROW_RELEASE_VERSION.matcher(providedFile.getName());
|
||||
if(matcher.find()) {
|
||||
String version = matcher.group();
|
||||
@@ -482,6 +491,15 @@ public class DownloadVerifierDialog extends Dialog<ButtonBar.ButtonData> {
|
||||
}
|
||||
|
||||
private File findReleaseFile(File manifestFile, Map<File, String> manifestMap) {
|
||||
File initialFile = initial.get();
|
||||
if(initialFile != null) {
|
||||
for(File file : manifestMap.keySet()) {
|
||||
if(initialFile.getName().equals(file.getName())) {
|
||||
return initialFile;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
List<String> releaseExtensions = getReleaseFileExtensions();
|
||||
List<List<String>> extensionLists = List.of(releaseExtensions, DISK_IMAGE_EXTENSIONS, ARCHIVE_EXTENSIONS, List.of(""));
|
||||
|
||||
@@ -565,7 +583,7 @@ public class DownloadVerifierDialog extends Dialog<ButtonBar.ButtonData> {
|
||||
}
|
||||
}
|
||||
|
||||
if(name.startsWith(SPARROW_RELEASE_PREFIX) && file.length() >= MIN_VALID_SPARROW_RELEASE_SIZE) {
|
||||
if((name.startsWith(SPARROW_RELEASE_PREFIX) || name.startsWith(SPARROW_RELEASE_ALT_PREFIX)) && file.length() >= MIN_VALID_SPARROW_RELEASE_SIZE) {
|
||||
Matcher matcher = SPARROW_RELEASE_VERSION.matcher(name);
|
||||
return matcher.find();
|
||||
}
|
||||
@@ -578,6 +596,10 @@ public class DownloadVerifierDialog extends Dialog<ButtonBar.ButtonData> {
|
||||
signature.set(signatureFile);
|
||||
}
|
||||
|
||||
public void setInitialFile(File initialFile) {
|
||||
initial.set(initialFile);
|
||||
}
|
||||
|
||||
private static class Header extends GridPane {
|
||||
public Header() {
|
||||
setMaxWidth(Double.MAX_VALUE);
|
||||
|
||||
@@ -825,7 +825,7 @@ public class EntryCell extends TreeTableCell<Entry, Entry> implements Confirmati
|
||||
confirmationsListener.getConfirmationsProperty().unbind();
|
||||
}
|
||||
}
|
||||
if(OsType.getCurrent() == OsType.MACOS && transactionEntry.getBlockTransaction().getHeight() > 0) {
|
||||
if(OsType.getCurrent() == OsType.MACOS && transactionEntry.getBlockTransaction().getHeight() > 0 && !cell.getStyleClass().contains("label-cell")) {
|
||||
cell.getStyleClass().add("number-field");
|
||||
}
|
||||
} else if(entry instanceof NodeEntry) {
|
||||
@@ -835,7 +835,7 @@ public class EntryCell extends TreeTableCell<Entry, Entry> implements Confirmati
|
||||
if(!utxoEntry.isSpendable()) {
|
||||
cell.getStyleClass().add("unspendable");
|
||||
}
|
||||
if(OsType.getCurrent() == OsType.MACOS && utxoEntry.getHashIndex().getHeight() > 0 && !addressCell) {
|
||||
if(OsType.getCurrent() == OsType.MACOS && utxoEntry.getHashIndex().getHeight() > 0 && !addressCell && !cell.getStyleClass().contains("label-cell")) {
|
||||
cell.getStyleClass().add("number-field");
|
||||
}
|
||||
} else if(entry instanceof HashIndexEntry hashIndexEntry) {
|
||||
|
||||
@@ -168,7 +168,7 @@ public class FileWalletExportPane extends TitledDescriptionPane {
|
||||
qrDisplayDialog = new QRDisplayDialog(RegistryType.BYTES.toString(), outputStream.toByteArray(), true);
|
||||
} else if(exporter instanceof PassportMultisig || exporter instanceof KeystoneMultisig || exporter instanceof JadeMultisig) {
|
||||
qrDisplayDialog = new QRDisplayDialog(RegistryType.BYTES.toString(), outputStream.toByteArray(), false);
|
||||
} else if(exporter instanceof Bip129) {
|
||||
} else if(exporter instanceof Bip129 || exporter instanceof WalletLabels) {
|
||||
UR ur = UR.fromBytes(outputStream.toByteArray());
|
||||
BBQR bbqr = new BBQR(BBQRType.UNICODE, outputStream.toByteArray());
|
||||
qrDisplayDialog = new QRDisplayDialog(ur, bbqr, false, false, false);
|
||||
|
||||
+3
-1
@@ -55,7 +55,7 @@ public class MnemonicWalletKeystoreImportPane extends MnemonicKeystorePane {
|
||||
protected List<Node> createRightButtons() {
|
||||
discoverButton = new Button("Discover Wallet");
|
||||
discoverButton.setDisable(true);
|
||||
discoverButton.setDefaultButton(true);
|
||||
discoverButton.setDefaultButton(AppServices.onlineProperty().get());
|
||||
discoverButton.managedProperty().bind(discoverButton.visibleProperty());
|
||||
discoverButton.setOnAction(event -> {
|
||||
discoverWallet();
|
||||
@@ -66,6 +66,7 @@ public class MnemonicWalletKeystoreImportPane extends MnemonicKeystorePane {
|
||||
|
||||
importButton = new Button("Import Wallet");
|
||||
importButton.setDisable(true);
|
||||
importButton.setDefaultButton(!AppServices.onlineProperty().get());
|
||||
importButton.managedProperty().bind(importButton.visibleProperty());
|
||||
importButton.visibleProperty().bind(discoverButton.visibleProperty().not());
|
||||
importButton.setOnAction(event -> {
|
||||
@@ -196,6 +197,7 @@ public class MnemonicWalletKeystoreImportPane extends MnemonicKeystorePane {
|
||||
HBox.setHgrow(region, Priority.SOMETIMES);
|
||||
|
||||
Button importMnemonicButton = new Button("Import");
|
||||
importMnemonicButton.setDefaultButton(true);
|
||||
importMnemonicButton.setOnAction(event -> {
|
||||
showHideLink.setVisible(true);
|
||||
setExpanded(false);
|
||||
|
||||
@@ -3,6 +3,7 @@ package com.sparrowwallet.sparrow.control;
|
||||
import com.sparrowwallet.drongo.KeyPurpose;
|
||||
import com.sparrowwallet.drongo.OsType;
|
||||
import com.sparrowwallet.drongo.address.Address;
|
||||
import com.sparrowwallet.drongo.bip47.PaymentCode;
|
||||
import com.sparrowwallet.drongo.protocol.Sha256Hash;
|
||||
import com.sparrowwallet.drongo.protocol.TransactionOutput;
|
||||
import com.sparrowwallet.drongo.uri.BitcoinURI;
|
||||
@@ -702,7 +703,9 @@ public class TransactionDiagram extends GridPane {
|
||||
paymentBox.getChildren().addAll(region, amountLabel);
|
||||
}
|
||||
|
||||
outputNodes.add(new OutputNode(paymentBox, payment.getAddress(), payment.getAmount()));
|
||||
Wallet bip47Wallet = toWallet != null && toWallet.isBip47() ? toWallet : (toBip47Wallet != null && toBip47Wallet.isBip47() ? toBip47Wallet : null);
|
||||
PaymentCode paymentCode = bip47Wallet == null ? null : bip47Wallet.getKeystores().getFirst().getExternalPaymentCode();
|
||||
outputNodes.add(new OutputNode(paymentBox, payment.getAddress(), payment.getAmount(), paymentCode));
|
||||
}
|
||||
|
||||
Set<Integer> seenIndexes = new HashSet<>();
|
||||
@@ -766,7 +769,7 @@ public class TransactionDiagram extends GridPane {
|
||||
outputsBox.getChildren().add(outputNode.outputLabel);
|
||||
outputsBox.getChildren().add(createSpacer());
|
||||
|
||||
ContextMenu contextMenu = new LabelContextMenu(outputNode.address, outputNode.amount);
|
||||
ContextMenu contextMenu = new LabelContextMenu(outputNode.address, outputNode.amount, outputNode.paymentCode);
|
||||
if(!outputNode.outputLabel.getChildren().isEmpty() && outputNode.outputLabel.getChildren().get(0) instanceof Label outputLabelControl) {
|
||||
outputLabelControl.setContextMenu(contextMenu);
|
||||
}
|
||||
@@ -1073,16 +1076,26 @@ public class TransactionDiagram extends GridPane {
|
||||
public Pane outputLabel;
|
||||
public Address address;
|
||||
public long amount;
|
||||
public PaymentCode paymentCode;
|
||||
|
||||
public OutputNode(Pane outputLabel, Address address, long amount) {
|
||||
this(outputLabel, address, amount, null);
|
||||
}
|
||||
|
||||
public OutputNode(Pane outputLabel, Address address, long amount, PaymentCode paymentCode) {
|
||||
this.outputLabel = outputLabel;
|
||||
this.address = address;
|
||||
this.amount = amount;
|
||||
this.paymentCode = paymentCode;
|
||||
}
|
||||
}
|
||||
|
||||
private class LabelContextMenu extends ContextMenu {
|
||||
public LabelContextMenu(Address address, long value) {
|
||||
this(address, value, null);
|
||||
}
|
||||
|
||||
public LabelContextMenu(Address address, long value, PaymentCode paymentCode) {
|
||||
if(address != null) {
|
||||
MenuItem copyAddress = new MenuItem("Copy Address");
|
||||
copyAddress.setOnAction(event -> {
|
||||
@@ -1119,6 +1132,17 @@ public class TransactionDiagram extends GridPane {
|
||||
Clipboard.getSystemClipboard().setContent(content);
|
||||
});
|
||||
getItems().addAll(copySatsValue, copyBtcValue);
|
||||
|
||||
if(paymentCode != null) {
|
||||
MenuItem copyPaymentCode = new MenuItem("Copy Payment Code");
|
||||
copyPaymentCode.setOnAction(AE -> {
|
||||
hide();
|
||||
ClipboardContent content = new ClipboardContent();
|
||||
content.putString(paymentCode.toString());
|
||||
Clipboard.getSystemClipboard().setContent(content);
|
||||
});
|
||||
getItems().add(copyPaymentCode);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,7 +28,8 @@ public class UsbStatusButton extends MenuButton {
|
||||
for(Device device : devices) {
|
||||
MenuItem deviceItem = new MenuItem(device.getModel().toDisplayString());
|
||||
if(!device.isNeedsPinSent() && (device.getModel() == WalletModel.TREZOR_1 || device.getModel() == WalletModel.TREZOR_T || device.getModel() == WalletModel.TREZOR_SAFE_3 ||
|
||||
device.getModel() == WalletModel.TREZOR_SAFE_5 || device.getModel() == WalletModel.KEEPKEY || device.getModel() == WalletModel.BITBOX_02)) {
|
||||
device.getModel() == WalletModel.TREZOR_SAFE_5 || device.getModel() == WalletModel.KEEPKEY || device.getModel() == WalletModel.BITBOX_02 ||
|
||||
device.getModel() == WalletModel.ONEKEY_CLASSIC_1S || device.getModel() == WalletModel.ONEKEY_PRO)) {
|
||||
deviceItem = new Menu(device.getModel().toDisplayString());
|
||||
MenuItem toggleItem = new MenuItem("Toggle Passphrase" + (!device.getModel().externalPassphraseEntry() ? "" : (device.isNeedsPassphraseSent() ? " Off" : " On")));
|
||||
toggleItem.setOnAction(event -> {
|
||||
|
||||
@@ -18,8 +18,8 @@ import java.util.List;
|
||||
public class WalletExportDialog extends Dialog<Wallet> {
|
||||
private Wallet wallet;
|
||||
|
||||
public WalletExportDialog(WalletForm walletForm) {
|
||||
this.wallet = walletForm.getWallet();
|
||||
public WalletExportDialog(WalletForm selectedWalletForm, List<WalletForm> allWalletForms) {
|
||||
this.wallet = selectedWalletForm.getWallet();
|
||||
|
||||
EventManager.get().register(this);
|
||||
setOnCloseRequest(event -> {
|
||||
@@ -45,10 +45,10 @@ public class WalletExportDialog extends Dialog<Wallet> {
|
||||
|
||||
List<WalletExport> exporters;
|
||||
if(wallet.getPolicyType() == PolicyType.SINGLE) {
|
||||
exporters = List.of(new Electrum(), new ElectrumPersonalServer(), new Descriptor(), new SpecterDesktop(), new Sparrow(), new WalletLabels(), new WalletTransactions(walletForm));
|
||||
exporters = List.of(new Electrum(), new ElectrumPersonalServer(), new Descriptor(), new SpecterDesktop(), new Sparrow(), new WalletLabels(allWalletForms), new WalletTransactions(selectedWalletForm));
|
||||
} else if(wallet.getPolicyType() == PolicyType.MULTI) {
|
||||
exporters = List.of(new Bip129(), new CaravanMultisig(), new ColdcardMultisig(), new CoboVaultMultisig(), new Electrum(), new ElectrumPersonalServer(), new KeystoneMultisig(),
|
||||
new Descriptor(), new JadeMultisig(), new PassportMultisig(), new SpecterDesktop(), new BlueWalletMultisig(), new SpecterDIY(), new Sparrow(), new WalletLabels(), new WalletTransactions(walletForm));
|
||||
new Descriptor(), new JadeMultisig(), new PassportMultisig(), new SpecterDesktop(), new BlueWalletMultisig(), new SpecterDIY(), new Sparrow(), new WalletLabels(allWalletForms), new WalletTransactions(selectedWalletForm));
|
||||
} else {
|
||||
throw new UnsupportedOperationException("Cannot export wallet with policy type " + wallet.getPolicyType());
|
||||
}
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
package com.sparrowwallet.sparrow.io;
|
||||
|
||||
import com.sparrowwallet.drongo.KeyDerivation;
|
||||
import com.sparrowwallet.drongo.KeyPurpose;
|
||||
import com.sparrowwallet.drongo.OutputDescriptor;
|
||||
import com.sparrowwallet.drongo.wallet.Keystore;
|
||||
import com.sparrowwallet.drongo.wallet.Wallet;
|
||||
import com.sparrowwallet.drongo.wallet.WalletModel;
|
||||
import com.sparrowwallet.sparrow.wallet.KeystoreController;
|
||||
|
||||
import java.io.*;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
@@ -92,7 +95,7 @@ public class Descriptor implements WalletImport, WalletExport {
|
||||
InputStream secondClone = new ByteArrayInputStream(baos.toByteArray());
|
||||
|
||||
try {
|
||||
return PdfUtils.getOutputDescriptor(firstClone).toWallet();
|
||||
return ensureKeyDerivations(PdfUtils.getOutputDescriptor(firstClone).toWallet());
|
||||
} catch(Exception e) {
|
||||
//ignore
|
||||
}
|
||||
@@ -100,7 +103,7 @@ public class Descriptor implements WalletImport, WalletExport {
|
||||
List<String> paragraphs = getParagraphs(secondClone);
|
||||
for(String paragraph : paragraphs) {
|
||||
OutputDescriptor descriptor = OutputDescriptor.getOutputDescriptor(paragraph);
|
||||
return descriptor.toWallet();
|
||||
return ensureKeyDerivations(descriptor.toWallet());
|
||||
}
|
||||
|
||||
throw new ImportException("Could not find an output descriptor in the file");
|
||||
@@ -116,7 +119,7 @@ public class Descriptor implements WalletImport, WalletExport {
|
||||
BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8));
|
||||
for(String line : reader.lines().map(String::trim).toArray(String[]::new)) {
|
||||
if(line.isEmpty()) {
|
||||
if(paragraph.length() > 0) {
|
||||
if(!paragraph.isEmpty()) {
|
||||
paragraphs.add(paragraph.toString());
|
||||
paragraph.setLength(0);
|
||||
}
|
||||
@@ -127,13 +130,23 @@ public class Descriptor implements WalletImport, WalletExport {
|
||||
}
|
||||
}
|
||||
|
||||
if(paragraph.length() > 0) {
|
||||
if(!paragraph.isEmpty()) {
|
||||
paragraphs.add(paragraph.toString());
|
||||
}
|
||||
|
||||
return paragraphs;
|
||||
}
|
||||
|
||||
private static Wallet ensureKeyDerivations(Wallet wallet) {
|
||||
for(Keystore keystore : wallet.getKeystores()) {
|
||||
if(keystore.getKeyDerivation().getMasterFingerprint() == null || keystore.getKeyDerivation().getDerivationPath() == null) {
|
||||
keystore.setKeyDerivation(new KeyDerivation(KeystoreController.DEFAULT_WATCH_ONLY_FINGERPRINT, wallet.getScriptType().getDefaultDerivationPath()));
|
||||
}
|
||||
}
|
||||
|
||||
return wallet;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isWalletImportScannable() {
|
||||
return true;
|
||||
|
||||
@@ -54,9 +54,9 @@ public class Hwi {
|
||||
Lark lark = getLark(passphrase);
|
||||
isPromptActive = true;
|
||||
return lark.enumerate().stream().map(Device::fromHardwareClient).toList();
|
||||
} catch(Exception e) {
|
||||
} catch(Throwable e) {
|
||||
log.error("Error enumerating USB devices", e);
|
||||
throw new ImportException("Error scanning" + (e.getMessage() == null || e.getMessage().isEmpty() ? ", check devices are ready" : ": " + e.getMessage()), e);
|
||||
throw new ImportException(e.getMessage() == null || e.getMessage().isEmpty() ? "Error scanning, check devices are ready" : e.getMessage(), e);
|
||||
} finally {
|
||||
isPromptActive = false;
|
||||
}
|
||||
|
||||
@@ -1,33 +1,41 @@
|
||||
package com.sparrowwallet.sparrow.io;
|
||||
|
||||
import com.csvreader.CsvReader;
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.*;
|
||||
import com.sparrowwallet.drongo.KeyPurpose;
|
||||
import com.sparrowwallet.drongo.OutputDescriptor;
|
||||
import com.sparrowwallet.drongo.Utils;
|
||||
import com.sparrowwallet.drongo.protocol.Sha256Hash;
|
||||
import com.sparrowwallet.drongo.protocol.Transaction;
|
||||
import com.sparrowwallet.drongo.wallet.*;
|
||||
import com.sparrowwallet.sparrow.AppServices;
|
||||
import com.sparrowwallet.sparrow.EventManager;
|
||||
import com.sparrowwallet.sparrow.event.KeystoreLabelsChangedEvent;
|
||||
import com.sparrowwallet.sparrow.event.WalletEntryLabelsChangedEvent;
|
||||
import com.sparrowwallet.sparrow.event.WalletUtxoStatusChangedEvent;
|
||||
import com.sparrowwallet.sparrow.net.ExchangeSource;
|
||||
import com.sparrowwallet.sparrow.wallet.*;
|
||||
import org.apache.commons.lang3.time.DateUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.*;
|
||||
import java.math.BigDecimal;
|
||||
import java.math.RoundingMode;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.text.DateFormat;
|
||||
import java.text.ParseException;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
public class WalletLabels implements WalletImport, WalletExport {
|
||||
private static final Logger log = LoggerFactory.getLogger(WalletLabels.class);
|
||||
private static final long ONE_DAY = 24*60*60*1000L;
|
||||
|
||||
private final List<WalletForm> walletForms;
|
||||
|
||||
public WalletLabels() {
|
||||
this.walletForms = Collections.emptyList();
|
||||
}
|
||||
|
||||
public WalletLabels(List<WalletForm> walletForms) {
|
||||
this.walletForms = walletForms;
|
||||
}
|
||||
@@ -50,8 +58,9 @@ public class WalletLabels implements WalletImport, WalletExport {
|
||||
@Override
|
||||
public void exportWallet(Wallet wallet, OutputStream outputStream, String password) throws ExportException {
|
||||
List<Label> labels = new ArrayList<>();
|
||||
List<Wallet> allWallets = wallet.isMasterWallet() ? wallet.getAllWallets() : wallet.getMasterWallet().getAllWallets();
|
||||
for(Wallet exportWallet : allWallets) {
|
||||
Map<Date, Double> fiatRates = getFiatRates(walletForms);
|
||||
for(WalletForm exportWalletForm : walletForms) {
|
||||
Wallet exportWallet = exportWalletForm.getWallet();
|
||||
OutputDescriptor outputDescriptor = OutputDescriptor.getOutputDescriptor(exportWallet);
|
||||
String origin = outputDescriptor.toString(true, false, false);
|
||||
|
||||
@@ -61,34 +70,43 @@ public class WalletLabels implements WalletImport, WalletExport {
|
||||
}
|
||||
}
|
||||
|
||||
for(BlockTransaction blkTx : exportWallet.getWalletTransactions().values()) {
|
||||
if(blkTx.getLabel() != null && !blkTx.getLabel().isEmpty()) {
|
||||
labels.add(new Label(Type.tx, blkTx.getHashAsString(), blkTx.getLabel(), origin, null));
|
||||
Set<Sha256Hash> confirmingTxs = new HashSet<>();
|
||||
WalletTransactionsEntry walletTransactionsEntry = exportWalletForm.getWalletTransactionsEntry();
|
||||
for(Entry entry : walletTransactionsEntry.getChildren()) {
|
||||
TransactionEntry txEntry = (TransactionEntry)entry;
|
||||
BlockTransaction blkTx = txEntry.getBlockTransaction();
|
||||
labels.add(new TransactionLabel(blkTx.getHashAsString(), blkTx.getLabel(), origin,
|
||||
txEntry.isConfirming() ? null : blkTx.getHeight(), blkTx.getDate(),
|
||||
blkTx.getFee() == null || blkTx.getFee() == 0 ? null : blkTx.getFee(), txEntry.getValue(),
|
||||
getFiatValue(blkTx.getDate(), Transaction.SATOSHIS_PER_BITCOIN, fiatRates)));
|
||||
if(txEntry.isConfirming()) {
|
||||
confirmingTxs.add(blkTx.getHash());
|
||||
}
|
||||
}
|
||||
|
||||
for(WalletNode addressNode : exportWallet.getWalletAddresses().values()) {
|
||||
if(addressNode.getLabel() != null && !addressNode.getLabel().isEmpty()) {
|
||||
labels.add(new Label(Type.addr, addressNode.getAddress().toString(), addressNode.getLabel(), null, null));
|
||||
}
|
||||
labels.add(new AddressLabel(addressNode.getAddress().toString(), addressNode.getLabel(), origin, addressNode.getDerivationPath().substring(1),
|
||||
addressNode.getTransactionOutputs().stream().flatMap(txo -> txo.isSpent() ? Stream.of(txo, txo.getSpentBy()) : Stream.of(txo))
|
||||
.filter(ref -> !confirmingTxs.contains(ref.getHash())).map(BlockTransactionHash::getHeight).toList()));
|
||||
}
|
||||
|
||||
for(BlockTransactionHashIndex txo : exportWallet.getWalletTxos().keySet()) {
|
||||
String spendable = (txo.isSpent() ? null : txo.getStatus() == Status.FROZEN ? "false" : "true");
|
||||
if(txo.getLabel() != null && !txo.getLabel().isEmpty()) {
|
||||
labels.add(new Label(Type.output, txo.toString(), txo.getLabel(), null, spendable));
|
||||
} else if(!txo.isSpent()) {
|
||||
labels.add(new Label(Type.output, txo.toString(), null, null, spendable));
|
||||
}
|
||||
for(Map.Entry<BlockTransactionHashIndex, WalletNode> txoEntry : exportWallet.getWalletTxos().entrySet()) {
|
||||
BlockTransactionHashIndex txo = txoEntry.getKey();
|
||||
WalletNode addressNode = txoEntry.getValue();
|
||||
Boolean spendable = (txo.isSpent() ? null : txo.getStatus() != Status.FROZEN);
|
||||
labels.add(new InputOutputLabel(Type.output, txo.toString(), txo.getLabel(), origin, spendable, addressNode.getDerivationPath().substring(1), txo.getValue(),
|
||||
confirmingTxs.contains(txo.getHash()) ? null : txo.getHeight(), txo.getDate(), getFiatValue(txo, fiatRates)));
|
||||
|
||||
if(txo.isSpent() && txo.getSpentBy().getLabel() != null && !txo.getSpentBy().getLabel().isEmpty()) {
|
||||
labels.add(new Label(Type.input, txo.getSpentBy().toString(), txo.getSpentBy().getLabel(), null, null));
|
||||
if(txo.isSpent()) {
|
||||
BlockTransactionHashIndex txi = txo.getSpentBy();
|
||||
labels.add(new InputOutputLabel(Type.input, txi.toString(), txi.getLabel(), origin, null, addressNode.getDerivationPath().substring(1), txi.getValue(),
|
||||
confirmingTxs.contains(txi.getHash()) ? null : txi.getHeight(), txi.getDate(), getFiatValue(txi, fiatRates)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
Gson gson = new Gson();
|
||||
Gson gson = new GsonBuilder().registerTypeAdapter(Date.class, new GsonUTCDateAdapter()).create();
|
||||
BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(outputStream, StandardCharsets.UTF_8));
|
||||
|
||||
for(Label label : labels) {
|
||||
@@ -114,7 +132,7 @@ public class WalletLabels implements WalletImport, WalletExport {
|
||||
|
||||
@Override
|
||||
public boolean isWalletExportScannable() {
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -247,11 +265,11 @@ public class WalletLabels implements WalletImport, WalletExport {
|
||||
addChangedEntry(changedWalletEntries, txioEntry);
|
||||
}
|
||||
|
||||
if(label.type == Type.output && !reference.isSpent()) {
|
||||
if("false".equalsIgnoreCase(label.spendable) && reference.getStatus() != Status.FROZEN) {
|
||||
if(label.type == Type.output && !reference.isSpent() && label.spendable != null) {
|
||||
if(!label.spendable && reference.getStatus() != Status.FROZEN) {
|
||||
reference.setStatus(Status.FROZEN);
|
||||
addChangedUtxo(changedWalletUtxoStatuses, txioEntry);
|
||||
} else if("true".equalsIgnoreCase(label.spendable) && reference.getStatus() == Status.FROZEN) {
|
||||
} else if(label.spendable && reference.getStatus() == Status.FROZEN) {
|
||||
reference.setStatus(null);
|
||||
addChangedUtxo(changedWalletUtxoStatuses, txioEntry);
|
||||
}
|
||||
@@ -316,7 +334,7 @@ public class WalletLabels implements WalletImport, WalletExport {
|
||||
|
||||
@Override
|
||||
public boolean isWalletImportScannable() {
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -324,12 +342,77 @@ public class WalletLabels implements WalletImport, WalletExport {
|
||||
return true;
|
||||
}
|
||||
|
||||
private Map<Date, Double> getFiatRates(List<WalletForm> walletForms) {
|
||||
ExchangeSource exchangeSource = getExchangeSource();
|
||||
Currency fiatCurrency = getFiatCurrency();
|
||||
Map<Date, Double> fiatRates = new HashMap<>();
|
||||
if(fiatCurrency != null) {
|
||||
long min = Long.MAX_VALUE;
|
||||
long max = Long.MIN_VALUE;
|
||||
|
||||
for(WalletForm walletForm : walletForms) {
|
||||
WalletTransactionsEntry walletTransactionsEntry = walletForm.getWalletTransactionsEntry();
|
||||
if(!walletTransactionsEntry.getChildren().isEmpty()) {
|
||||
LongSummaryStatistics stats = walletTransactionsEntry.getChildren().stream()
|
||||
.map(entry -> ((TransactionEntry)entry).getBlockTransaction().getDate())
|
||||
.filter(Objects::nonNull)
|
||||
.collect(Collectors.summarizingLong(Date::getTime));
|
||||
min = Math.min(min, stats.getMin());
|
||||
max = Math.max(max, stats.getMax());
|
||||
}
|
||||
}
|
||||
|
||||
if(max > min) {
|
||||
fiatRates = exchangeSource.getHistoricalExchangeRates(fiatCurrency, new Date(min - ONE_DAY), new Date(max));
|
||||
}
|
||||
}
|
||||
|
||||
return fiatRates;
|
||||
}
|
||||
|
||||
private static ExchangeSource getExchangeSource() {
|
||||
return Config.get().getExchangeSource() == null ? ExchangeSource.COINGECKO : Config.get().getExchangeSource();
|
||||
}
|
||||
|
||||
private static Currency getFiatCurrency() {
|
||||
return getExchangeSource() == ExchangeSource.NONE || !AppServices.onlineProperty().get() ? null : Config.get().getFiatCurrency();
|
||||
}
|
||||
|
||||
private Map<Currency, BigDecimal> getFiatValue(TransactionEntry txEntry, Map<Date, Double> fiatRates) {
|
||||
return getFiatValue(txEntry.getBlockTransaction().getDate(), txEntry.getValue(), fiatRates);
|
||||
}
|
||||
|
||||
private Map<Currency, BigDecimal> getFiatValue(BlockTransactionHashIndex ref, Map<Date, Double> fiatRates) {
|
||||
return getFiatValue(ref.getDate(), ref.getValue(), fiatRates);
|
||||
}
|
||||
|
||||
private Map<Currency, BigDecimal> getFiatValue(Date date, long value, Map<Date, Double> fiatRates) {
|
||||
Currency fiatCurrency = getFiatCurrency();
|
||||
if(fiatCurrency != null) {
|
||||
Double dayRate = null;
|
||||
if(date == null) {
|
||||
if(AppServices.getFiatCurrencyExchangeRate() != null) {
|
||||
dayRate = AppServices.getFiatCurrencyExchangeRate().getBtcRate();
|
||||
}
|
||||
} else {
|
||||
dayRate = fiatRates.get(DateUtils.truncate(date, Calendar.DAY_OF_MONTH));
|
||||
}
|
||||
|
||||
if(dayRate != null) {
|
||||
BigDecimal fiatValue = BigDecimal.valueOf(dayRate * value / Transaction.SATOSHIS_PER_BITCOIN);
|
||||
return Map.of(fiatCurrency, fiatValue.setScale(fiatCurrency.getDefaultFractionDigits(), RoundingMode.HALF_UP));
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private enum Type {
|
||||
tx, addr, pubkey, input, output, xpub
|
||||
}
|
||||
|
||||
private static class Label {
|
||||
public Label(Type type, String ref, String label, String origin, String spendable) {
|
||||
public Label(Type type, String ref, String label, String origin, Boolean spendable) {
|
||||
this.type = type;
|
||||
this.ref = ref;
|
||||
this.label = label;
|
||||
@@ -341,6 +424,74 @@ public class WalletLabels implements WalletImport, WalletExport {
|
||||
String ref;
|
||||
String label;
|
||||
String origin;
|
||||
String spendable;
|
||||
Boolean spendable;
|
||||
}
|
||||
|
||||
private static class TransactionLabel extends Label {
|
||||
public TransactionLabel(String ref, String label, String origin, Integer height, Date time, Long fee, Long value, Map<Currency, BigDecimal> rate) {
|
||||
super(Type.tx, ref, label, origin, null);
|
||||
this.height = height;
|
||||
this.time = time;
|
||||
this.fee = fee;
|
||||
this.value = value;
|
||||
this.rate = rate;
|
||||
}
|
||||
|
||||
Integer height;
|
||||
Date time;
|
||||
Long fee;
|
||||
Long value;
|
||||
Map<Currency, BigDecimal> rate;
|
||||
}
|
||||
|
||||
private static class AddressLabel extends Label {
|
||||
public AddressLabel(String ref, String label, String origin, String keypath, List<Integer> heights) {
|
||||
super(Type.addr, ref, label, origin, null);
|
||||
this.keypath = keypath;
|
||||
this.heights = heights;
|
||||
}
|
||||
|
||||
String keypath;
|
||||
List<Integer> heights;
|
||||
}
|
||||
|
||||
private static class InputOutputLabel extends Label {
|
||||
public InputOutputLabel(Type type, String ref, String label, String origin, Boolean spendable, String keypath, Long value, Integer height, Date time, Map<Currency, BigDecimal> fmv) {
|
||||
super(type, ref, label, origin, spendable);
|
||||
this.keypath = keypath;
|
||||
this.value = value;
|
||||
this.height = height;
|
||||
this.time = time;
|
||||
this.fmv = fmv;
|
||||
}
|
||||
|
||||
String keypath;
|
||||
Long value;
|
||||
Integer height;
|
||||
Date time;
|
||||
Map<Currency, BigDecimal> fmv;
|
||||
}
|
||||
|
||||
public static class GsonUTCDateAdapter implements JsonSerializer<Date>, JsonDeserializer<Date> {
|
||||
private final DateFormat dateFormat;
|
||||
|
||||
public GsonUTCDateAdapter() {
|
||||
dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'");
|
||||
dateFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
|
||||
}
|
||||
|
||||
@Override
|
||||
public JsonElement serialize(Date src, java.lang.reflect.Type typeOfSrc, JsonSerializationContext context) {
|
||||
return new JsonPrimitive(dateFormat.format(src));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Date deserialize(JsonElement json, java.lang.reflect.Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
|
||||
try {
|
||||
return dateFormat.parse(json.getAsString());
|
||||
} catch (ParseException e) {
|
||||
throw new JsonParseException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ package com.sparrowwallet.sparrow.keystoreimport;
|
||||
import com.google.common.eventbus.Subscribe;
|
||||
import com.sparrowwallet.drongo.KeyDerivation;
|
||||
import com.sparrowwallet.drongo.Network;
|
||||
import com.sparrowwallet.drongo.protocol.ScriptType;
|
||||
import com.sparrowwallet.drongo.wallet.Keystore;
|
||||
import com.sparrowwallet.drongo.wallet.KeystoreSource;
|
||||
import com.sparrowwallet.drongo.wallet.Wallet;
|
||||
@@ -10,6 +11,7 @@ import com.sparrowwallet.drongo.wallet.WalletModel;
|
||||
import com.sparrowwallet.sparrow.AppServices;
|
||||
import com.sparrowwallet.sparrow.EventManager;
|
||||
import com.sparrowwallet.sparrow.event.KeystoreImportEvent;
|
||||
import com.sparrowwallet.sparrow.wallet.KeystoreController;
|
||||
import javafx.fxml.FXMLLoader;
|
||||
import javafx.scene.control.*;
|
||||
import org.controlsfx.tools.Borders;
|
||||
@@ -20,16 +22,18 @@ import java.util.List;
|
||||
public class KeystoreImportDialog extends Dialog<Keystore> {
|
||||
private final KeystoreImportController keystoreImportController;
|
||||
private Keystore keystore;
|
||||
private final ScriptType scriptType;
|
||||
private final String existingLabel;
|
||||
|
||||
public KeystoreImportDialog(Wallet wallet) {
|
||||
this(wallet, KeystoreSource.HW_USB);
|
||||
}
|
||||
|
||||
public KeystoreImportDialog(Wallet wallet, KeystoreSource initialSource) {
|
||||
this(wallet, initialSource, null, null, false);
|
||||
this(wallet, initialSource, null, null, Keystore.DEFAULT_LABEL, false);
|
||||
}
|
||||
|
||||
public KeystoreImportDialog(Wallet wallet, KeystoreSource initialSource, KeyDerivation currentDerivation, WalletModel currentModel, boolean restrictImport) {
|
||||
public KeystoreImportDialog(Wallet wallet, KeystoreSource initialSource, KeyDerivation currentDerivation, WalletModel currentModel, String currentLabel, boolean restrictImport) {
|
||||
EventManager.get().register(this);
|
||||
setOnCloseRequest(event -> {
|
||||
EventManager.get().unregister(this);
|
||||
@@ -37,6 +41,8 @@ public class KeystoreImportDialog extends Dialog<Keystore> {
|
||||
|
||||
final DialogPane dialogPane = getDialogPane();
|
||||
AppServices.setStageIcon(dialogPane.getScene().getWindow());
|
||||
scriptType = wallet.getScriptType();
|
||||
existingLabel = currentLabel;
|
||||
|
||||
try {
|
||||
FXMLLoader ksiLoader = new FXMLLoader(AppServices.class.getResource("keystoreimport/keystoreimport.fxml"));
|
||||
@@ -73,9 +79,10 @@ public class KeystoreImportDialog extends Dialog<Keystore> {
|
||||
|
||||
private Keystore getWatchOnlyKeystore() {
|
||||
this.keystore = new Keystore();
|
||||
keystore.setLabel(existingLabel);
|
||||
keystore.setSource(KeystoreSource.SW_WATCH);
|
||||
keystore.setWalletModel(WalletModel.SPARROW);
|
||||
keystore.setKeyDerivation(new KeyDerivation("",""));
|
||||
keystore.setKeyDerivation(new KeyDerivation(KeystoreController.DEFAULT_WATCH_ONLY_FINGERPRINT, scriptType.getDefaultDerivationPath()));
|
||||
return keystore;
|
||||
}
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@ import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public enum ExchangeSource {
|
||||
NONE("None") {
|
||||
NONE("None", null) {
|
||||
@Override
|
||||
public List<Currency> getSupportedCurrencies() {
|
||||
return Collections.emptyList();
|
||||
@@ -37,7 +37,7 @@ public enum ExchangeSource {
|
||||
return Collections.emptyMap();
|
||||
}
|
||||
},
|
||||
COINBASE("Coinbase") {
|
||||
COINBASE("Coinbase", "No historical rates") {
|
||||
@Override
|
||||
public List<Currency> getSupportedCurrencies() {
|
||||
return getRates().data.rates.keySet().stream().filter(code -> isValidISO4217Code(code.toUpperCase(Locale.ROOT)))
|
||||
@@ -121,7 +121,7 @@ public enum ExchangeSource {
|
||||
return historicalRates;
|
||||
}
|
||||
},
|
||||
COINGECKO("Coingecko") {
|
||||
COINGECKO("Coingecko", "No historical rates") {
|
||||
@Override
|
||||
public List<Currency> getSupportedCurrencies() {
|
||||
return getRates().rates.entrySet().stream().filter(rate -> "fiat".equals(rate.getValue().type) && isValidISO4217Code(rate.getKey().toUpperCase(Locale.ROOT)))
|
||||
@@ -189,7 +189,7 @@ public enum ExchangeSource {
|
||||
return historicalRates;
|
||||
}
|
||||
},
|
||||
MEMPOOL_SPACE("mempool.space") {
|
||||
MEMPOOL_SPACE("mempool.space", "Historical rates from Apr 2023") {
|
||||
@Override
|
||||
public List<Currency> getSupportedCurrencies() {
|
||||
return getRates().rates.entrySet().stream().filter(price -> isValidISO4217Code(price.getKey().toUpperCase(Locale.ROOT)))
|
||||
@@ -262,9 +262,11 @@ public enum ExchangeSource {
|
||||
private static final Logger log = LoggerFactory.getLogger(ExchangeSource.class);
|
||||
|
||||
private final String name;
|
||||
private final String description;
|
||||
|
||||
ExchangeSource(String name) {
|
||||
ExchangeSource(String name, String description) {
|
||||
this.name = name;
|
||||
this.description = description;
|
||||
}
|
||||
|
||||
public abstract List<Currency> getSupportedCurrencies();
|
||||
@@ -282,6 +284,14 @@ public enum ExchangeSource {
|
||||
}
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public String getDescription() {
|
||||
return description;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return name;
|
||||
|
||||
@@ -129,7 +129,7 @@ public enum Protocol {
|
||||
public abstract CloseableTransport getTransport(HostAndPort server, File serverCert, HostAndPort proxy) throws IOException, CertificateException, NoSuchAlgorithmException, KeyStoreException, KeyManagementException;
|
||||
|
||||
public HostAndPort getServerHostAndPort(String url) {
|
||||
String lessProtocol = url.substring(this.toUrlString().length());
|
||||
String lessProtocol = url.endsWith(":t") || url.endsWith(":s") ? url.substring(0, url.length() - 2) : url.substring(this.toUrlString().length());
|
||||
int pathStart = lessProtocol.indexOf('/');
|
||||
return HostAndPort.fromString(pathStart < 0 ? lessProtocol : lessProtocol.substring(0, pathStart));
|
||||
}
|
||||
@@ -178,10 +178,10 @@ public enum Protocol {
|
||||
}
|
||||
|
||||
public static Protocol getProtocol(String url) {
|
||||
if(url.startsWith("tcp://")) {
|
||||
if(url.startsWith("tcp://") || url.endsWith(":t")) {
|
||||
return TCP;
|
||||
}
|
||||
if(url.startsWith("ssl://")) {
|
||||
if(url.startsWith("ssl://") || url.endsWith(":s")) {
|
||||
return SSL;
|
||||
}
|
||||
if(url.startsWith("http://")) {
|
||||
|
||||
@@ -19,6 +19,7 @@ import javafx.collections.ObservableList;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.control.ComboBox;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.ListCell;
|
||||
import javafx.util.StringConverter;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
@@ -158,6 +159,9 @@ public class GeneralSettingsController extends SettingsDetailController {
|
||||
config.setExchangeSource(exchangeSource.getValue());
|
||||
}
|
||||
|
||||
exchangeSource.setButtonCell(new ExchangeSourceButtonCell());
|
||||
exchangeSource.setCellFactory(_ -> new ExchangeSourceListCell());
|
||||
|
||||
exchangeSource.valueProperty().addListener((observable, oldValue, source) -> {
|
||||
config.setExchangeSource(source);
|
||||
updateCurrencies(source);
|
||||
@@ -253,4 +257,32 @@ public class GeneralSettingsController extends SettingsDetailController {
|
||||
|
||||
fiatCurrency.valueProperty().addListener(fiatCurrencyListener);
|
||||
}
|
||||
|
||||
private static class ExchangeSourceButtonCell extends ListCell<ExchangeSource> {
|
||||
@Override
|
||||
protected void updateItem(ExchangeSource exchangeSource, boolean empty) {
|
||||
super.updateItem(exchangeSource, empty);
|
||||
if(exchangeSource == null || empty) {
|
||||
setText("");
|
||||
} else {
|
||||
setText(exchangeSource.getName());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static class ExchangeSourceListCell extends ListCell<ExchangeSource> {
|
||||
@Override
|
||||
protected void updateItem(ExchangeSource exchangeSource, boolean empty) {
|
||||
super.updateItem(exchangeSource, empty);
|
||||
if(exchangeSource == null || empty) {
|
||||
setText("");
|
||||
} else {
|
||||
String text = exchangeSource.getName();
|
||||
if(exchangeSource.getDescription() != null && !exchangeSource.getDescription().isEmpty()) {
|
||||
text += " (" + exchangeSource.getDescription() + ")";
|
||||
}
|
||||
setText(text);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ import com.sparrowwallet.drongo.psbt.PSBT;
|
||||
import com.sparrowwallet.drongo.psbt.PSBTInput;
|
||||
import com.sparrowwallet.drongo.uri.BitcoinURI;
|
||||
import com.sparrowwallet.drongo.wallet.*;
|
||||
import com.sparrowwallet.hummingbird.UR;
|
||||
import com.sparrowwallet.hummingbird.registry.CryptoPSBT;
|
||||
import com.sparrowwallet.sparrow.AppServices;
|
||||
import com.sparrowwallet.sparrow.EventManager;
|
||||
@@ -227,6 +228,9 @@ public class HeadersController extends TransactionFormController implements Init
|
||||
@FXML
|
||||
private Button broadcastButton;
|
||||
|
||||
@FXML
|
||||
private Button showTransactionButton;
|
||||
|
||||
@FXML
|
||||
private Button saveFinalButton;
|
||||
|
||||
@@ -461,6 +465,8 @@ public class HeadersController extends TransactionFormController implements Init
|
||||
broadcastProgressBar.visibleProperty().bind(signaturesProgressBar.visibleProperty().not());
|
||||
|
||||
broadcastButton.managedProperty().bind(broadcastButton.visibleProperty());
|
||||
showTransactionButton.managedProperty().bind(showTransactionButton.visibleProperty());
|
||||
showTransactionButton.visibleProperty().bind(broadcastButton.visibleProperty().not());
|
||||
saveFinalButton.managedProperty().bind(saveFinalButton.visibleProperty());
|
||||
saveFinalButton.visibleProperty().bind(broadcastButton.visibleProperty().not());
|
||||
broadcastButton.visibleProperty().bind(AppServices.onlineProperty());
|
||||
@@ -1255,6 +1261,21 @@ public class HeadersController extends TransactionFormController implements Init
|
||||
broadcastTransactionService.start();
|
||||
}
|
||||
|
||||
public void showTransaction(ActionEvent event) {
|
||||
try {
|
||||
Transaction transaction = headersForm.getPsbt().extractTransaction();
|
||||
byte[] txBytes = transaction.bitcoinSerialize();
|
||||
UR ur = UR.fromBytes(txBytes);
|
||||
BBQR bbqr = new BBQR(BBQRType.TXN, txBytes);
|
||||
QRDisplayDialog qrDisplayDialog = new QRDisplayDialog(ur, bbqr, false, false, false);
|
||||
qrDisplayDialog.initOwner(showTransactionButton.getScene().getWindow());
|
||||
qrDisplayDialog.showAndWait();
|
||||
} catch (Exception exception) {
|
||||
log.error("Error creating UR", exception);
|
||||
AppServices.showErrorDialog("Error displaying transaction QR code", exception.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public void saveFinalTransaction(ActionEvent event) {
|
||||
Stage window = new Stage();
|
||||
|
||||
|
||||
@@ -36,10 +36,7 @@ import tornadofx.control.Field;
|
||||
|
||||
import javax.smartcardio.CardException;
|
||||
import java.net.URL;
|
||||
import java.util.Base64;
|
||||
import java.util.Locale;
|
||||
import java.util.Optional;
|
||||
import java.util.ResourceBundle;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static com.sparrowwallet.sparrow.io.CardApi.isReaderAvailable;
|
||||
@@ -256,6 +253,7 @@ public class KeystoreController extends WalletFormController implements Initiali
|
||||
launchImportDialog(keystoreSource);
|
||||
} else {
|
||||
fingerprint.setText(DEFAULT_WATCH_ONLY_FINGERPRINT);
|
||||
derivation.setText(getWalletForm().getWallet().getScriptType().getDefaultDerivationPath());
|
||||
selectSourcePane.setVisible(false);
|
||||
}
|
||||
}
|
||||
@@ -370,7 +368,8 @@ public class KeystoreController extends WalletFormController implements Initiali
|
||||
boolean restrictImport = keystore.getSource() != KeystoreSource.SW_WATCH && keystoreSourceToggleGroup.getToggles().stream().anyMatch(toggle -> ((ToggleButton)toggle).isDisabled());
|
||||
KeyDerivation currentDerivation = keystore.getKeyDerivation();
|
||||
WalletModel currentModel = keystore.getWalletModel();
|
||||
KeystoreImportDialog dlg = new KeystoreImportDialog(getWalletForm().getWallet(), initialSource, currentDerivation, currentModel, restrictImport);
|
||||
String currentLabel = keystore.getLabel();
|
||||
KeystoreImportDialog dlg = new KeystoreImportDialog(getWalletForm().getWallet(), initialSource, currentDerivation, currentModel, currentLabel, restrictImport);
|
||||
dlg.initOwner(selectSourcePane.getScene().getWindow());
|
||||
Optional<Keystore> result = dlg.showAndWait();
|
||||
if(result.isPresent()) {
|
||||
@@ -385,7 +384,12 @@ public class KeystoreController extends WalletFormController implements Initiali
|
||||
return;
|
||||
}
|
||||
}
|
||||
walletForm.getWallet().makeLabelsUnique(importedKeystore);
|
||||
if(!keystore.getLabel().equals(importedKeystore.getLabel())) {
|
||||
List<Keystore> changedKeystores = walletForm.getWallet().makeLabelsUnique(importedKeystore);
|
||||
if(!changedKeystores.isEmpty()) {
|
||||
EventManager.get().post(new SettingsChangedEvent(walletForm.getWallet(), SettingsChangedEvent.Type.KEYSTORE_LABEL));
|
||||
}
|
||||
}
|
||||
keystore.setSource(importedKeystore.getSource());
|
||||
keystore.setWalletModel(importedKeystore.getWalletModel());
|
||||
keystore.setLabel(importedKeystore.getLabel());
|
||||
@@ -395,7 +399,7 @@ public class KeystoreController extends WalletFormController implements Initiali
|
||||
keystore.setSeed(importedKeystore.getSeed());
|
||||
keystore.setBip47ExtendedPrivateKey(importedKeystore.getBip47ExtendedPrivateKey());
|
||||
|
||||
updateType(true);
|
||||
updateType(keystore.isValid());
|
||||
label.setText(keystore.getLabel());
|
||||
fingerprint.setText(keystore.getKeyDerivation().getMasterFingerprint());
|
||||
derivation.setText(keystore.getKeyDerivation().getDerivationPath());
|
||||
@@ -652,15 +656,31 @@ public class KeystoreController extends WalletFormController implements Initiali
|
||||
|
||||
@Subscribe
|
||||
public void update(SettingsChangedEvent event) {
|
||||
if(walletForm.getWallet().equals(event.getWallet()) && event.getType().equals(SettingsChangedEvent.Type.SCRIPT_TYPE)) {
|
||||
derivation.setPromptText(event.getWallet().getScriptType().getDefaultDerivationPath());
|
||||
if(derivation.getText() != null && !derivation.getText().isEmpty()) {
|
||||
String derivationPath = derivation.getText();
|
||||
derivation.setText(derivationPath + " ");
|
||||
derivation.setText(derivationPath);
|
||||
if(walletForm.getWallet().equals(event.getWallet())) {
|
||||
if(event.getType().equals(SettingsChangedEvent.Type.SCRIPT_TYPE)) {
|
||||
if(keystore.getSource() == KeystoreSource.SW_WATCH && derivation.getPromptText().equals(derivation.getText())) {
|
||||
derivation.setText(event.getWallet().getScriptType().getDefaultDerivationPath());
|
||||
}
|
||||
derivation.setPromptText(event.getWallet().getScriptType().getDefaultDerivationPath());
|
||||
if(derivation.getText() != null && !derivation.getText().isEmpty()) {
|
||||
String derivationPath = derivation.getText();
|
||||
derivation.setText(derivationPath + " ");
|
||||
derivation.setText(derivationPath);
|
||||
}
|
||||
if(keystore.getExtendedPublicKey() != null) {
|
||||
setXpubContext(keystore.getExtendedPublicKey());
|
||||
}
|
||||
} else if(event.getType().equals(SettingsChangedEvent.Type.KEYSTORE_LABEL)) {
|
||||
if(!keystore.getLabel().equals(label.getText())) {
|
||||
label.setText(keystore.getLabel());
|
||||
}
|
||||
}
|
||||
if(keystore.getExtendedPublicKey() != null) {
|
||||
setXpubContext(keystore.getExtendedPublicKey());
|
||||
|
||||
if(event.getType().equals(SettingsChangedEvent.Type.KEYSTORE_LABEL) || event.getType().equals(SettingsChangedEvent.Type.KEYSTORE_FINGERPRINT) ||
|
||||
event.getType().equals(SettingsChangedEvent.Type.KEYSTORE_DERIVATION) || event.getType().equals(SettingsChangedEvent.Type.KEYSTORE_XPUB)) {
|
||||
if(keystore.getSource() == KeystoreSource.SW_WATCH) {
|
||||
exportButton.setVisible(keystore.isValid() && getWalletForm().getWallet().getPolicyType() == PolicyType.MULTI);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -591,7 +591,7 @@ public class SettingsController extends WalletFormController implements Initiali
|
||||
}
|
||||
|
||||
if(walletForm instanceof SettingsWalletForm settingsWalletForm) {
|
||||
WalletExportDialog dlg = new WalletExportDialog(settingsWalletForm.getAppWalletForm());
|
||||
WalletExportDialog dlg = new WalletExportDialog(settingsWalletForm.getAppWalletForm(), List.of(settingsWalletForm.getAppWalletForm()));
|
||||
dlg.initOwner(export.getScene().getWindow());
|
||||
dlg.showAndWait();
|
||||
} else {
|
||||
|
||||
@@ -52,7 +52,7 @@
|
||||
<ComboBox fx:id="fiatCurrency" />
|
||||
</Field>
|
||||
<Field text="Exchange rate source:">
|
||||
<ComboBox fx:id="exchangeSource">
|
||||
<ComboBox fx:id="exchangeSource" prefWidth="160">
|
||||
<items>
|
||||
<FXCollections fx:factory="observableArrayList">
|
||||
<ExchangeSource fx:constant="NONE" />
|
||||
|
||||
@@ -285,6 +285,11 @@
|
||||
<Glyph fontFamily="Font Awesome 5 Free Solid" fontSize="20" icon="SATELLITE_DISH" />
|
||||
</graphic>
|
||||
</Button>
|
||||
<Button fx:id="showTransactionButton" HBox.hgrow="ALWAYS" text="Show Transaction as QR" contentDisplay="TOP" wrapText="true" textAlignment="CENTER" onAction="#showTransaction">
|
||||
<graphic>
|
||||
<Glyph fontFamily="Font Awesome 5 Free Solid" fontSize="20" icon="QRCODE" />
|
||||
</graphic>
|
||||
</Button>
|
||||
<Button fx:id="saveFinalButton" defaultButton="true" HBox.hgrow="ALWAYS" text="Save Final Transaction" contentDisplay="TOP" wrapText="true" textAlignment="CENTER" onAction="#saveFinalTransaction">
|
||||
<graphic>
|
||||
<Glyph fontFamily="Font Awesome 5 Free Solid" fontSize="20" icon="ARROW_DOWN" />
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="15px" height="15px" viewBox="0 0 15 15" version="1.1">
|
||||
<g id="surface1">
|
||||
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(92.941177%,92.941177%,92.941177%);fill-opacity:1;" d="M 7.5 12.898438 C 11.226562 12.898438 12.898438 11.226562 12.898438 7.5 C 12.898438 3.773438 11.226562 2.101562 7.5 2.101562 C 3.773438 2.101562 2.101562 3.773438 2.101562 7.5 C 2.101562 11.226562 3.773438 12.898438 7.5 12.898438 Z M 7.5 12.898438 "/>
|
||||
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(12.941177%,12.941177%,12.941177%);fill-opacity:1;" d="M 6.484375 4.390625 L 7.988281 4.390625 L 7.988281 6.867188 L 7.054688 6.867188 L 7.054688 5.1875 L 6.222656 5.1875 Z M 6.484375 4.390625 "/>
|
||||
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(12.941177%,12.941177%,12.941177%);fill-opacity:1;" d="M 7.5 10.609375 C 8.445312 10.609375 9.214844 9.84375 9.214844 8.898438 C 9.214844 7.949219 8.445312 7.183594 7.5 7.183594 C 6.554688 7.183594 5.785156 7.949219 5.785156 8.898438 C 5.785156 9.84375 6.554688 10.609375 7.5 10.609375 Z M 7.5 10.609375 "/>
|
||||
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(92.941177%,92.941177%,92.941177%);fill-opacity:1;" d="M 7.5 9.832031 C 8.015625 9.832031 8.433594 9.414062 8.433594 8.898438 C 8.433594 8.378906 8.015625 7.960938 7.5 7.960938 C 6.984375 7.960938 6.566406 8.378906 6.566406 8.898438 C 6.566406 9.414062 6.984375 9.832031 7.5 9.832031 Z M 7.5 9.832031 "/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.5 KiB |
@@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="15px" height="15px" viewBox="0 0 15 15" version="1.1">
|
||||
<g id="surface1">
|
||||
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(20%,20%,20%);fill-opacity:1;" d="M 7.5 12.898438 C 11.226562 12.898438 12.898438 11.226562 12.898438 7.5 C 12.898438 3.773438 11.226562 2.101562 7.5 2.101562 C 3.773438 2.101562 2.101562 3.773438 2.101562 7.5 C 2.101562 11.226562 3.773438 12.898438 7.5 12.898438 Z M 7.5 12.898438 "/>
|
||||
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(92.941177%,92.941177%,92.941177%);fill-opacity:1;" d="M 6.484375 4.390625 L 7.988281 4.390625 L 7.988281 6.867188 L 7.054688 6.867188 L 7.054688 5.1875 L 6.222656 5.1875 Z M 6.484375 4.390625 "/>
|
||||
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(92.941177%,92.941177%,92.941177%);fill-opacity:1;" d="M 7.5 10.609375 C 8.445312 10.609375 9.214844 9.84375 9.214844 8.898438 C 9.214844 7.949219 8.445312 7.183594 7.5 7.183594 C 6.554688 7.183594 5.785156 7.949219 5.785156 8.898438 C 5.785156 9.84375 6.554688 10.609375 7.5 10.609375 Z M 7.5 10.609375 "/>
|
||||
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(20%,20%,20%);fill-opacity:1;" d="M 7.5 9.832031 C 8.015625 9.832031 8.433594 9.414062 8.433594 8.898438 C 8.433594 8.378906 8.015625 7.960938 7.5 7.960938 C 6.984375 7.960938 6.566406 8.378906 6.566406 8.898438 C 6.566406 9.414062 6.984375 9.832031 7.5 9.832031 Z M 7.5 9.832031 "/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.5 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 1.2 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 2.4 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 5.3 KiB |
@@ -13,6 +13,7 @@
|
||||
<logger name="org.flywaydb.core.internal.command.DbValidate" level="WARN" />
|
||||
<logger name="org.flywaydb.core.internal.command.DbMigrate" level="WARN" />
|
||||
<logger name="org.flywaydb.core.internal.command.DbClean" level="ERROR" />
|
||||
<logger name="org.flywaydb.core.internal.command.clean.CleanExecutor" level="ERROR" />
|
||||
<logger name="org.flywaydb.core.internal.database.base.BaseDatabaseType" level="WARN" />
|
||||
<logger name="org.flywaydb.core.internal.database.base.Schema" level="WARN" />
|
||||
<logger name="org.flywaydb.core.internal.schemahistory.JdbcTableSchemaHistory" level="WARN" />
|
||||
|
||||
Reference in New Issue
Block a user