add frigate.2140.dev public electrum server and auto-select based on requirements for open wallets
What changed, and why it matters
This commit adds a new default public Electrum server (frigate.2140.dev) to the Sparrow Bitcoin wallet and changes how the app picks a public server when a connection fails. The new server is advertised as supporting 'Silent Payments' (a newer Bitcoin address type), and the app now tries to choose only servers that can handle the types of wallets currently open. There is no direct evidence in the commit of a security vulnerability, but adding a new third-party server and changing server-selection logic can affect user privacy and reliability.
Review the trustworthiness and operational security of frigate.2140.dev before it ships as a default server; verify that policy-type filtering cannot exclude all servers and leave the wallet in an unexpected offline state; consider whether users need explicit notice when a public server is auto-selected.
Security signals we found
New third-party default server added to wallet's public server list
Server selection logic changed to filter by wallet policy-type compatibility
Connection-failure retry messaging and timing changed
Random server selection moved from java.util.Random to ThreadLocalRandom
Evidence from the diff
The patch introduces FRIGATE_2140_DEV as a new mainnet public Electrum server and adds per-server supported PolicyType lists. It moves public-server rotation logic from Config.java into AppServices.java, where rotation now filters servers by whether they support all policy types of currently open wallets. UI cells in server settings are updated to highlight Silent Payments support, and ThreadLocalRandom replaces Random. The change is defensive in nature (avoid connecting to servers that cannot serve the wallet), but it also adds a new trusted default endpoint and changes failure/retry behavior.
Changed components
PublicElectrumServer enum and server listAppServices connection/reconnection logicConfig public server configurationServerSettingsController UITerminal PublicElectrumDialog UIInspect captured patch +97 / −21
diff --git a/src/main/java/com/sparrowwallet/sparrow/AppServices.java b/src/main/java/com/sparrowwallet/sparrow/AppServices.java
index 5bd3c7e..71d078f 100644
--- a/src/main/java/com/sparrowwallet/sparrow/AppServices.java
+++ b/src/main/java/com/sparrowwallet/sparrow/AppServices.java
@@ -69,9 +69,11 @@ import java.time.temporal.ChronoUnit;
import java.util.*;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
+import static com.sparrowwallet.sparrow.AppController.CONNECTION_FAILED_PREFIX;
import static com.sparrowwallet.sparrow.control.DownloadVerifierDialog.*;
public class AppServices {
@@ -367,15 +369,18 @@ public class AppServices {
onlineProperty.setValue(false);
onlineProperty.addListener(onlineServicesListener);
+ log.debug("Connection failed", failEvent.getSource().getException());
if(Config.get().getServerType() == ServerType.PUBLIC_ELECTRUM_SERVER) {
- Config.get().changePublicServer();
- connectionService.setPeriod(Duration.seconds(PUBLIC_SERVER_RETRY_PERIOD_SECS));
+ boolean changed = changePublicServer();
+ connectionService.setPeriod(changed ? Duration.seconds(PUBLIC_SERVER_RETRY_PERIOD_SECS) : Duration.seconds(PRIVATE_SERVER_RETRY_PERIOD_SECS));
+ EventManager.get().post(new ConnectionFailedEvent(failEvent.getSource().getException()));
+ if(!changed) {
+ Platform.runLater(() -> EventManager.get().post(new StatusEvent(CONNECTION_FAILED_PREFIX + "No public servers available that can serve the open wallets, retrying later...")));
+ }
} else {
connectionService.setPeriod(Duration.seconds(PRIVATE_SERVER_RETRY_PERIOD_SECS));
+ EventManager.get().post(new ConnectionFailedEvent(failEvent.getSource().getException()));
}
-
- log.debug("Connection failed", failEvent.getSource().getException());
- EventManager.get().post(new ConnectionFailedEvent(failEvent.getSource().getException()));
});
return connectionService;
@@ -866,6 +871,22 @@ public class AppServices {
return Storage.isWalletFile(file);
}
+ public boolean changePublicServer() {
+ List<PolicyType> policyTypes = getOpenWallets().keySet().stream().map(Wallet::getPolicyType).filter(Objects::nonNull).collect(Collectors.toList());
+ return changePublicServer(policyTypes.isEmpty() ? List.of(PolicyType.SINGLE_HD) : policyTypes);
+ }
+
+ private boolean changePublicServer(List<PolicyType> policyTypes) {
+ Config config = Config.get();
+ List<Server> otherServers = PublicElectrumServer.getServers().stream().filter(pes -> pes.supportsAllPolicyTypes(policyTypes))
+ .map(PublicElectrumServer::getServer).filter(server -> !server.equals(config.getPublicElectrumServer())).collect(Collectors.toList());
+ if(!otherServers.isEmpty()) {
+ config.setPublicElectrumServer(otherServers.get(ThreadLocalRandom.current().nextInt(otherServers.size())));
+ return true;
+ }
+ return false;
+ }
+
public static Optional<ButtonType> showWarningDialog(String title, String content, ButtonType... buttons) {
return showAlertDialog(title, content, Alert.AlertType.WARNING, buttons);
}
@@ -1463,9 +1484,16 @@ public class AppServices {
@Subscribe
public void walletHistoryFailed(WalletHistoryFailedEvent event) {
if(Config.get().getServerType() == ServerType.PUBLIC_ELECTRUM_SERVER && isConnected()) {
+ String currentName = Config.get().getServerDisplayName();
onlineProperty.set(false);
- log.warn("Failed to fetch wallet history from " + Config.get().getServerDisplayName() + ", reconnecting to another server...");
- Config.get().changePublicServer();
+ boolean changed = changePublicServer();
+ if(changed) {
+ log.warn("Failed to fetch wallet history from " + currentName + ", reconnecting to another server...");
+ } else {
+ log.warn("Failed to fetch wallet history from " + currentName + ", retrying later");
+ connectionService.setDelay(Duration.seconds(PRIVATE_SERVER_RETRY_PERIOD_SECS));
+ EventManager.get().post(new StatusEvent("Wallet load failed: No other public servers available that can serve the open wallets, retrying later..."));
+ }
onlineProperty.set(true);
}
}
diff --git a/src/main/java/com/sparrowwallet/sparrow/io/Config.java b/src/main/java/com/sparrowwallet/sparrow/io/Config.java
index 51793fd..0055831 100644
--- a/src/main/java/com/sparrowwallet/sparrow/io/Config.java
+++ b/src/main/java/com/sparrowwallet/sparrow/io/Config.java
@@ -556,13 +556,6 @@ public class Config {
flush();
}
- public void changePublicServer() {
- List<Server> otherServers = PublicElectrumServer.getServers().stream().map(PublicElectrumServer::getServer).filter(server -> !server.equals(getPublicElectrumServer())).collect(Collectors.toList());
- if(!otherServers.isEmpty()) {
- setPublicElectrumServer(otherServers.get(new Random().nextInt(otherServers.size())));
- }
- }
-
public Server getCoreServer() {
return coreServer;
}
diff --git a/src/main/java/com/sparrowwallet/sparrow/net/PublicElectrumServer.java b/src/main/java/com/sparrowwallet/sparrow/net/PublicElectrumServer.java
index ff5fef4..a20d303 100644
--- a/src/main/java/com/sparrowwallet/sparrow/net/PublicElectrumServer.java
+++ b/src/main/java/com/sparrowwallet/sparrow/net/PublicElectrumServer.java
@@ -2,6 +2,7 @@ package com.sparrowwallet.sparrow.net;
import com.google.common.net.HostAndPort;
import com.sparrowwallet.drongo.Network;
+import com.sparrowwallet.drongo.policy.PolicyType;
import com.sparrowwallet.sparrow.io.Server;
import java.util.Arrays;
@@ -20,17 +21,24 @@ public enum PublicElectrumServer {
TESTNET_QTORNADO_COM("testnet.qtornado.com", "ssl://testnet.qtornado.com:51002", Network.TESTNET),
SIGNET_MEMPOOL_SPACE("mempool.space", "ssl://mempool.space:60602", Network.SIGNET),
TESTNET4_MEMPOOL_SPACE("mempool.space", "ssl://mempool.space:40002", Network.TESTNET4),
- TESTNET4_C3_SOFT("blackie.c3-soft.com", "ssl://blackie.c3-soft.com:57010", Network.TESTNET4);
+ TESTNET4_C3_SOFT("blackie.c3-soft.com", "ssl://blackie.c3-soft.com:57010", Network.TESTNET4),
+ FRIGATE_2140_DEV("frigate.2140.dev", "ssl://frigate.2140.dev:50002", Network.MAINNET, List.of(PolicyType.SINGLE_HD, PolicyType.MULTI_HD, PolicyType.SINGLE_SP));
PublicElectrumServer(String name, String url, Network network) {
+ this(name, url, network, List.of(PolicyType.SINGLE_HD, PolicyType.MULTI_HD));
+ }
+
+ PublicElectrumServer(String name, String url, Network network, List<PolicyType> supportedPolicyTypes) {
this.server = new Server(url, name);
this.network = network;
+ this.supportedPolicyTypes = supportedPolicyTypes;
}
public static final List<Network> SUPPORTED_NETWORKS = List.of(Network.MAINNET, Network.TESTNET, Network.SIGNET, Network.TESTNET4);
private final Server server;
private final Network network;
+ private final List<PolicyType> supportedPolicyTypes;
public Server getServer() {
return server;
@@ -44,6 +52,14 @@ public enum PublicElectrumServer {
return network;
}
+ public boolean isSupportedPolicyType(PolicyType policyType) {
+ return supportedPolicyTypes.contains(policyType);
+ }
+
+ public boolean supportsAllPolicyTypes(List<PolicyType> policyTypes) {
+ return policyTypes.stream().allMatch(this::isSupportedPolicyType);
+ }
+
public static List<PublicElectrumServer> getServers() {
return Arrays.stream(values()).filter(server -> server.network == Network.get()).collect(Collectors.toList());
}
diff --git a/src/main/java/com/sparrowwallet/sparrow/settings/ServerSettingsController.java b/src/main/java/com/sparrowwallet/sparrow/settings/ServerSettingsController.java
index fbc3c17..0c3db13 100644
--- a/src/main/java/com/sparrowwallet/sparrow/settings/ServerSettingsController.java
+++ b/src/main/java/com/sparrowwallet/sparrow/settings/ServerSettingsController.java
@@ -6,6 +6,8 @@ import com.google.common.eventbus.Subscribe;
import com.google.common.net.HostAndPort;
import com.sparrowwallet.drongo.Network;
import com.sparrowwallet.drongo.OsType;
+import com.sparrowwallet.drongo.policy.PolicyType;
+import com.sparrowwallet.drongo.wallet.Wallet;
import com.sparrowwallet.sparrow.AppServices;
import com.sparrowwallet.sparrow.EventManager;
import com.sparrowwallet.sparrow.Mode;
@@ -47,10 +49,9 @@ import java.io.FileInputStream;
import java.security.cert.CertificateFactory;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Optional;
-import java.util.Random;
+import java.util.*;
+import java.util.concurrent.ThreadLocalRandom;
+import java.util.stream.Collectors;
public class ServerSettingsController extends SettingsDetailController {
private static final Logger log = LoggerFactory.getLogger(ServerSettingsController.class);
@@ -215,6 +216,9 @@ public class ServerSettingsController extends SettingsDetailController {
}
serverTypeToggleGroup.selectToggle(serverTypeToggleGroup.getToggles().stream().filter(toggle -> toggle.getUserData() == serverType).findFirst().orElse(null));
+ List<PolicyType> policyTypes = AppServices.get().getOpenWallets().keySet().stream().map(Wallet::getPolicyType).filter(Objects::nonNull).collect(Collectors.toList());
+ publicElectrumServer.setButtonCell(new PublicElectrumServerButtonCell());
+ publicElectrumServer.setCellFactory(_ -> new PublicElectrumServerListCell(policyTypes));
publicElectrumServer.setItems(FXCollections.observableList(PublicElectrumServer.getServers()));
publicElectrumServer.getSelectionModel().selectedItemProperty().addListener(getPublicElectrumServerListener(config));
@@ -442,7 +446,7 @@ public class ServerSettingsController extends SettingsDetailController {
if(configPublicElectrumServer == null && PublicElectrumServer.supportedNetwork()) {
List<PublicElectrumServer> servers = PublicElectrumServer.getServers();
if(!servers.isEmpty()) {
- publicElectrumServer.setValue(servers.get(new Random().nextInt(servers.size())));
+ publicElectrumServer.setValue(servers.get(ThreadLocalRandom.current().nextInt(servers.size())));
}
} else {
publicElectrumServer.setValue(configPublicElectrumServer);
@@ -1038,4 +1042,38 @@ public class ServerSettingsController extends SettingsDetailController {
}
}
}
+
+ private static class PublicElectrumServerButtonCell extends ListCell<PublicElectrumServer> {
+ @Override
+ protected void updateItem(PublicElectrumServer server, boolean empty) {
+ super.updateItem(server, empty);
+ if(server == null || empty) {
+ setText(null);
+ setGraphic(null);
+ } else {
+ setText(server.toString());
+ setGraphic(null);
+ }
+ }
+ }
+
+ private static class PublicElectrumServerListCell extends ListCell<PublicElectrumServer> {
+ private final List<PolicyType> openPolicyTypes;
+
+ public PublicElectrumServerListCell(List<PolicyType> openPolicyTypes) {
+ this.openPolicyTypes = openPolicyTypes;
+ }
+
+ @Override
+ protected void updateItem(PublicElectrumServer server, boolean empty) {
+ super.updateItem(server, empty);
+ if(server == null || empty) {
+ setText(null);
+ setGraphic(null);
+ } else {
+ setText(server + (openPolicyTypes.contains(PolicyType.SINGLE_SP) && server.isSupportedPolicyType(PolicyType.SINGLE_SP) ? " (supports Silent Payments)" : ""));
+ setGraphic(null);
+ }
+ }
+ }
}
diff --git a/src/main/java/com/sparrowwallet/sparrow/terminal/settings/PublicElectrumDialog.java b/src/main/java/com/sparrowwallet/sparrow/terminal/settings/PublicElectrumDialog.java
index 651b60c..034c004 100644
--- a/src/main/java/com/sparrowwallet/sparrow/terminal/settings/PublicElectrumDialog.java
+++ b/src/main/java/com/sparrowwallet/sparrow/terminal/settings/PublicElectrumDialog.java
@@ -2,6 +2,7 @@ package com.sparrowwallet.sparrow.terminal.settings;
import com.googlecode.lanterna.TerminalSize;
import com.googlecode.lanterna.gui2.*;
+import com.sparrowwallet.sparrow.AppServices;
import com.sparrowwallet.sparrow.io.Config;
import com.sparrowwallet.sparrow.net.PublicElectrumServer;
@@ -32,7 +33,7 @@ public class PublicElectrumDialog extends ServerProxyDialog {
url.addItem(server);
}
if(Config.get().getPublicElectrumServer() == null) {
- Config.get().changePublicServer();
+ AppServices.get().changePublicServer();
}
url.setSelectedItem(PublicElectrumServer.fromServer(Config.get().getPublicElectrumServer()));
url.addListener((selectedIndex, previousSelection, changedByUserInteraction) -> {
Why this scored 33/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.