A Java library for connecting to the BitcoinSV P2P network. Handles peer connections, the Bitcoin handshake, message serialization, health monitoring, automatic reconnection, and SPV chain tip tracking.
Ported from SpiffyNode (Dart).
- Java 21+
- Gradle 8.x
Gradle (Kotlin DSL):
dependencies {
implementation("org.twostack:spiffynode4j:0.1.0-SNAPSHOT")
}Gradle (Groovy):
implementation 'org.twostack:spiffynode4j:0.1.0-SNAPSHOT'import org.twostack.spiffynode4j.api.*;
import org.twostack.spiffynode4j.core.*;
import org.twostack.spiffynode4j.config.*;
import org.twostack.spiffynode4j.model.*;
import org.twostack.spiffynode4j.protocol.messages.*;
// 1. Register message types (once at startup)
MsgVersion.registerWithFactory();
MsgVerAck.registerWithFactory();
MsgPing.registerWithFactory();
MsgPong.registerWithFactory();
MsgInv.registerWithFactory();
MsgGetData.registerWithFactory();
MsgTx.registerWithFactory();
MsgBlock.registerWithFactory();
MsgHeaders.registerWithFactory();
// 2. Define your handler
PeerHandler handler = new PeerHandler.Default() {
@Override
public CompletableFuture<Void> handleTransaction(WireMessage msg, Peer peer) {
MsgTx tx = (MsgTx) msg;
System.out.println("Received TX: " + tx.getTxId());
return CompletableFuture.completedFuture(null);
}
@Override
public CompletableFuture<Void> handleBlockAnnouncement(InvVect item, Peer peer) {
System.out.println("New block: " + item.hash());
return CompletableFuture.completedFuture(null);
}
};
// 3. Create the manager and connect
PeerManagerConfig config = PeerManagerConfig.builder()
.maxPeers(8)
.enableReconnection(true)
.build();
try (PeerManager manager = new DefaultPeerManager(BitcoinNetwork.MAINNET, config, handler)) {
Peer peer = manager.addPeer("seed.bitcoinsv.io", 8333).join();
System.out.println("Connected: " + peer.getHost() + " (height unknown until version)");
// Keep running...
Thread.sleep(60_000);
}import org.twostack.spiffynode4j.spv.*;
import org.twostack.spiffynode4j.config.ChainTipTrackerConfig;
ChainTipTracker tracker = new ChainTipTracker(
ChainTipTrackerConfig.builder()
.minPeersForConsensus(3)
.consensusThreshold(0.6)
.build()
);
tracker.addListener(event -> {
System.out.println(event.getType() + ": height " + event.getNewTip().getHeight()
+ " (confidence " + event.getNewTip().getConfidence() + ")");
if (event.isReorganization()) {
System.out.println("REORG detected! " + event.getDescription());
}
});
// Wire it into the handler
PeerHandler spvHandler = new PeerHandler.Default() {
@Override
public CompletableFuture<Void> handleVersion(WireMessage msg, Peer peer) {
String peerId = peer.getHost() + ":" + peer.getPort();
tracker.registerPeer(peerId);
tracker.handleVersion(peerId, (MsgVersion) msg);
return CompletableFuture.completedFuture(null);
}
@Override
public CompletableFuture<Void> handleBlockAnnouncement(InvVect item, Peer peer) {
tracker.handleBlockAnnouncement(peer.getHost() + ":" + peer.getPort(), item.hash());
return CompletableFuture.completedFuture(null);
}
@Override
public CompletableFuture<Void> handleHeaders(WireMessage msg, Peer peer) {
tracker.handleHeaders(peer.getHost() + ":" + peer.getPort(), (MsgHeaders) msg);
return CompletableFuture.completedFuture(null);
}
};Or use the built-in ChainTipHandler which does the above automatically:
ChainTipHandler chainTipHandler = new ChainTipHandler(tracker);
CompositeHandler compositeHandler = new CompositeHandler(myHandler, chainTipHandler);// Per-peer
for (Peer peer : manager.getPeers()) {
PeerStatistics stats = peer.getStatistics();
System.out.printf("%s:%d — %s, %d msgs sent, %d received%n",
stats.host(), stats.port(), stats.state(),
stats.messagesSent(), stats.messagesReceived());
}
// Aggregate
ManagerStatistics stats = ((DefaultPeerManager) manager).getStatistics();
System.out.printf("Peers: %d connected, %d healthy, %d reconnecting%n",
stats.connectedPeers(), stats.healthyPeers(), stats.peersReconnecting());- API Guide — Use-case-driven walkthrough with examples
- Configuration Reference — All config properties with defaults
- Architecture — Layered design, state machine, message flow
./gradlew clean test376 tests across all 8 implementation phases.
See LICENSE for details.