Complete API reference for the SpiffyNode BitcoinSV P2P client library.
- Core Components
- Configuration
- Peer Management
- Message Handling
- Network Protocols
- SPV Features
- Error Handling
- Advanced Usage
- Examples
Must be called before using the library. Registers all Bitcoin protocol message types.
import 'package:spiffynode/spiffy_node.dart';
void main() {
initializeMessages(); // Required initialization
// ... rest of your code
}Supported Bitcoin networks with their magic numbers:
enum BitcoinNetwork {
mainnet(0xe8f3e1e3), // Production Bitcoin network
testnet(0xf4f3e5f4), // Test network
regtest(0xfabfb5da), // Regression testing
simnet(0x12141c16); // Simulation network
}Usage:
const network = BitcoinNetwork.regtest; // For local testing
const network = BitcoinNetwork.mainnet; // For productionConfiguration for individual peer connections:
class PeerConfig {
final Duration handshakeTimeout; // Handshake timeout (default: 30s)
final Duration pingInterval; // Ping frequency (default: 2m)
final Duration pongTimeout; // Pong response timeout (default: 30s)
final int maxUnhealthyScore; // Max unhealthy score before disconnect (default: 10)
final int protocolVersion; // Bitcoin protocol version (default: 70016)
final String userAgent; // Client identification (default: '/spiffynode:0.1.0/')
final bool enablePingPong; // Enable health monitoring (default: true)
final int startHeight; // Chain tip for SPV nodes (default: 0)
}Examples:
// Default configuration (sync from genesis)
const defaultConfig = PeerConfig();
// SPV node configuration
const spvConfig = PeerConfig(
startHeight: 50000, // Current chain tip
userAgent: '/my-spv-wallet:1.0/',
handshakeTimeout: Duration(seconds: 60),
enablePingPong: true,
);
// Mining pool configuration
const poolConfig = PeerConfig(
protocolVersion: 70016,
userAgent: '/mining-pool:2.1/',
pingInterval: Duration(seconds: 30), // More frequent health checks
maxUnhealthyScore: 5, // Lower tolerance
);Configuration for the peer manager:
class PeerManagerConfig {
final int minPeers; // Minimum connected peers (default: 3)
final int maxPeers; // Maximum connected peers (default: 8)
final Duration peerConnectTimeout; // Connection timeout (default: 30s)
final Duration announcementCooldown; // Rate limiting (default: 100ms)
final int maxAnnouncementBatch; // Batch size for announcements (default: 3)
final bool enableHealthMonitoring; // Health monitoring (default: true)
final Duration healthCheckInterval; // Health check frequency (default: 2m)
final PeerConfig defaultPeerConfig; // Default config for new peers
}Individual peer connection:
class Peer {
// Constructor
Peer({
required String address, // IP address or hostname
required int port, // Port number
required BitcoinNetwork network, // Bitcoin network
PeerHandlerI? handler, // Message handler (optional)
PeerConfig? config, // Configuration (optional)
Logger? logger, // Logger instance (optional)
});
// Connection management
Future<void> connect(); // Connect to peer
Future<void> shutdown(); // Disconnect and cleanup
// Properties
BitcoinNetwork get network; // Network this peer is on
bool get connected; // Connection status
bool get isHealthy; // Health status
PeerState get state; // Current state
bool get handshakeComplete; // Handshake status
MsgVersion? get remoteVersion; // Peer's version info
// Statistics
DateTime? get connectedAt; // Connection timestamp
int get messagesSent; // Messages sent count
int get messagesReceived; // Messages received count
int get pingsSent; // Pings sent count
int get pongsReceived; // Pongs received count
int get unhealthyScore; // Current health score
// Event streams
Stream<PeerState> get stateStream; // State change events
Stream<void> get isUnhealthyStream; // Health degradation events
}Peer States:
enum PeerState {
disconnected, // Not connected
connecting, // Establishing TCP connection
connected, // TCP connected, handshake pending
handshaking, // Performing Bitcoin handshake
ready, // Ready for Bitcoin protocol messages
unhealthy, // Peer is misbehaving
disconnecting, // Gracefully disconnecting
error, // Error state
}Manages multiple peer connections:
class PeerManager {
// Constructor
PeerManager({
required BitcoinNetwork network,
PeerHandlerI? handler,
PeerManagerConfig? config,
Logger? logger,
ChainTipTracker? chainTipTracker,
});
// Peer management
Future<Peer> addPeerByAddress(String address, int port, {PeerConfig? peerConfig});
Future<void> addPeer(Peer peer);
Future<void> removePeer(String peerId);
Future<void> addPeersByHostname(String hostname, {int port = 8333, PeerConfig? peerConfig});
// Properties
BitcoinNetwork get network; // Network
int get peerCount; // Connected peers count
int get healthyPeerCount; // Healthy peers count
bool get isShutdown; // Shutdown status
List<PeerI> getPeers(); // All peers
List<PeerI> getHealthyPeers(); // Healthy peers only
// Event streams
Stream<Peer> get peerAdded; // New peer connected
Stream<Peer> get peerRemoved; // Peer disconnected
Stream<WireMessage> get messageStream; // All received messages
// Chain management (SPV)
ChainTipTracker get chainTipTracker; // SPV chain tracker
// Cleanup
Future<void> shutdown(); // Shutdown all connections
}Interface for handling Bitcoin protocol messages:
abstract class PeerHandlerI {
// Transaction handling
Future<List<Uint8List>> handleTransactionsGet(List<InvVect> msgs, PeerI peer);
Future<void> handleTransactionSent(WireMessage msg, PeerI peer);
Future<void> handleTransactionAnnouncement(InvVect msg, PeerI peer);
Future<void> handleTransactionRejection(WireMessage rejMsg, PeerI peer);
Future<void> handleTransaction(WireMessage msg, PeerI peer);
// Block handling
Future<List<Uint8List>> handleBlocksGet(List<InvVect> msgs, PeerI peer);
Future<void> handleBlockSent(WireMessage msg, PeerI peer);
Future<void> handleBlockAnnouncement(InvVect msg, PeerI peer);
Future<void> handleBlock(WireMessage msg, PeerI peer);
// Protocol messages
Future<void> handleVersion(WireMessage msg, PeerI peer);
Future<void> handleAddresses(WireMessage msg, PeerI peer);
}Example Implementation:
class MyWalletHandler implements PeerHandlerI {
@override
Future<void> handleTransaction(WireMessage msg, PeerI peer) async {
final tx = msg as MsgTx;
print('Received transaction: ${tx.txHash}');
// Process transaction for your wallet
await processTransaction(tx);
}
@override
Future<void> handleBlock(WireMessage msg, PeerI peer) async {
final block = msg as MsgBlock;
print('Received block: ${block.header.hashString}');
// Process block headers for SPV
await updateChainTip(block.header);
}
@override
Future<void> handleVersion(WireMessage msg, PeerI peer) async {
final version = msg as MsgVersion;
print('Peer version: ${version.protocolVersion}, height: ${version.startHeight}');
}
// ... implement other methods
}Common Bitcoin protocol messages:
// Version handshake
MsgVersion(
protocolVersion: 70016,
services: ServiceFlags.none,
startHeight: 1000, // Your chain tip
userAgent: '/my-app:1.0/',
relay: false, // SPV nodes typically don't relay
);
// Block requests
MsgGetHeaders(
locatorHashes: [lastKnownBlockHash],
hashStop: Hash.zero, // Get all available
);
// Transaction
MsgTx(/* transaction data */);
// Inventory announcements
MsgInv([
InvVect(InvType.msgTx, transactionHash),
InvVect(InvType.msgBlock, blockHash),
]);
// Address announcements
MsgAddr([
NetworkAddress.ipv4('192.168.1.100', 8333),
]);Bitcoin node service capabilities:
class ServiceFlags {
static const int none = 0x00; // No services
static const int nodeNetwork = 0x01; // Full node
static const int nodeGetUtxo = 0x02; // UTXO queries
static const int nodeBloom = 0x04; // Bloom filters
static const int nodeWitness = 0x08; // Witness data
static const int nodeNetworkLimited = 0x0400; // Limited node
}IPv4/IPv6 network addresses:
// IPv4 address
final addr = NetworkAddress.ipv4('127.0.0.1', 8333);
// Manual construction
final addr = NetworkAddress(
services: ServiceFlags.nodeNetwork,
ip: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xFF, 0xFF, 127, 0, 0, 1], // IPv4-mapped
port: 8333,
timestamp: DateTime.now().millisecondsSinceEpoch ~/ 1000,
);
// Properties
bool get isIPv4; // IPv4 address check
bool get isLocalhost; // Localhost check
bool get isZero; // Invalid/zero address
String get ipString; // Human-readable IPMessage queue priority levels:
enum MessagePriority {
critical, // Protocol messages (version, verack, ping, pong)
high, // Block announcements, urgent data
normal, // Regular transactions, data requests
low, // Bulk data, background sync
}SPV chain synchronization:
class ChainTipTracker {
// Register/unregister peers
void registerPeer(String peerId, int reportedHeight);
void unregisterPeer(String peerId);
// Chain state
int get bestHeight; // Highest reported block
String get bestPeerId; // Peer with best chain
Map<String, int> get peerHeights; // All peer heights
// Health monitoring
bool isPeerBehind(String peerId, {int threshold = 144}); // ~24 hours
List<String> getStalePeers({int maxAge = Duration.secondsPerDay});
}Efficient SPV Sync:
// Get your current chain tip from local storage
final myChainTip = await getStoredChainTip(); // e.g., 50000
// Configure peers to sync only new blocks
const spvConfig = PeerConfig(
startHeight: myChainTip, // Don't sync from genesis
userAgent: '/my-spv-wallet:1.0/',
services: ServiceFlags.none, // We're not a full node
enablePingPong: true, // Monitor connection health
);
// Connect to multiple peers for redundancy
final peers = [
'seed1.bitcoin.sv:8333',
'seed2.bitcoin.sv:8333',
'seed3.bitcoin.sv:8333',
];
for (final peerAddr in peers) {
final [host, port] = peerAddr.split(':');
await peerManager.addPeerByAddress(
host,
int.parse(port),
peerConfig: spvConfig,
);
}Headers-First Sync:
class SPVHandler implements PeerHandlerI {
@override
Future<void> handleVersion(WireMessage msg, PeerI peer) async {
// After handshake, request block headers
final getHeaders = MsgGetHeaders(
locatorHashes: await getBlockLocators(), // Your known blocks
hashStop: Hash.zero, // Get all available
);
await peer.writeMessage(getHeaders);
}
@override
Future<void> handleHeaders(WireMessage msg, PeerI peer) async {
final headers = msg as MsgHeaders;
// Process headers sequentially
for (final header in headers.headers) {
await validateAndStoreHeader(header);
}
// Request more if we got the maximum batch
if (headers.headers.length == 2000) {
await requestMoreHeaders(peer);
}
}
}// Network exceptions
ConnectionException // Connection failed
ConnectionTimeoutException // Handshake/operation timeout
ConnectionLostException // Connection dropped unexpectedly
// Protocol exceptions
ProtocolException // Protocol violation
MessageException // Invalid message format
WireException // Wire protocol error
// Validation exceptions
ValidationException // Data validation failed
ChecksumException // Message checksum mismatchConnection Resilience:
Future<void> connectWithRetry(Peer peer, {int maxRetries = 3}) async {
for (int attempt = 1; attempt <= maxRetries; attempt++) {
try {
await peer.connect();
print('Connected successfully!');
return;
} on ConnectionTimeoutException {
print('Attempt $attempt: Connection timeout');
if (attempt == maxRetries) rethrow;
await Future.delayed(Duration(seconds: attempt * 2)); // Exponential backoff
} on ConnectionException catch (e) {
print('Attempt $attempt: Connection failed: $e');
if (attempt == maxRetries) rethrow;
await Future.delayed(Duration(seconds: attempt * 2));
}
}
}Message Handler Error Recovery:
class RobustHandler implements PeerHandlerI {
@override
Future<void> handleTransaction(WireMessage msg, PeerI peer) async {
try {
final tx = msg as MsgTx;
await processTransaction(tx);
} on ValidationException catch (e) {
logger.warning('Invalid transaction from ${peer.address}: $e');
// Don't disconnect peer for invalid transactions
} on Exception catch (e) {
logger.severe('Error processing transaction: $e');
// Consider disconnecting peer for serious errors
await peer.shutdown();
}
}
}Extending the protocol with custom messages:
class MyCustomMessage extends RegisterableMessage {
String customData;
MyCustomMessage({required this.customData});
@override
String get command => 'mycustom';
@override
int get maxPayloadLength => 1000;
@override
Uint8List encode(int protocolVersion, MessageEncoding encoding) {
return Uint8List.fromList(utf8.encode(customData));
}
@override
void decode(Uint8List data, int protocolVersion, MessageEncoding encoding) {
customData = utf8.decode(data);
}
static void registerWithFactory() {
RegisterableMessage.register<MyCustomMessage>(
'mycustom',
(data, version, encoding) {
final msg = MyCustomMessage(customData: '');
msg.decode(data, version, encoding);
return msg;
},
() => MyCustomMessage(customData: ''),
);
}
}class PerformanceMonitor implements PeerHandlerI {
final Map<String, int> _messageCount = {};
final Map<String, Duration> _processingTime = {};
@override
Future<void> handleTransaction(WireMessage msg, PeerI peer) async {
final stopwatch = Stopwatch()..start();
try {
// Your transaction processing
await processTransaction(msg as MsgTx);
} finally {
stopwatch.stop();
_messageCount['tx'] = (_messageCount['tx'] ?? 0) + 1;
_processingTime['tx'] = stopwatch.elapsed;
// Log performance metrics
if (_messageCount['tx']! % 100 == 0) {
print('Processed ${_messageCount['tx']} transactions, '
'avg time: ${_processingTime['tx']!.inMilliseconds}ms');
}
}
}
}import 'package:spiffynode/spiffy_node.dart';
class BitcoinMonitor implements PeerHandlerI {
@override
Future<void> handleBlockAnnouncement(InvVect msg, PeerI peer) async {
print('📦 New block announced: ${msg.hash}');
}
@override
Future<void> handleTransactionAnnouncement(InvVect msg, PeerI peer) async {
print('💰 New transaction: ${msg.hash}');
}
// ... implement other required methods
}
Future<void> main() async {
initializeMessages();
final monitor = BitcoinMonitor();
final peerManager = PeerManager(
network: BitcoinNetwork.mainnet,
handler: monitor,
);
// Connect to Bitcoin network
await peerManager.addPeerByAddress('seed.bitcoin.sv', 8333);
print('🚀 Bitcoin monitor started!');
// Keep running
await Future.delayed(Duration(hours: 24));
await peerManager.shutdown();
}class SPVWallet implements PeerHandlerI {
final Set<String> _watchedAddresses;
int _currentHeight = 0;
SPVWallet(this._watchedAddresses);
@override
Future<void> handleTransaction(WireMessage msg, PeerI peer) async {
final tx = msg as MsgTx;
// Check if transaction affects our addresses
for (final output in tx.outputs) {
final address = extractAddress(output.script);
if (_watchedAddresses.contains(address)) {
print('💰 Received ${output.value} satoshis to $address');
await notifyUser(address, output.value);
}
}
}
@override
Future<void> handleHeaders(WireMessage msg, PeerI peer) async {
final headers = msg as MsgHeaders;
for (final header in headers.headers) {
_currentHeight++;
await storeHeader(header, _currentHeight);
}
print('📊 Synced to block $_currentHeight');
}
}
Future<void> main() async {
initializeMessages();
// Your Bitcoin addresses to watch
final addresses = {
'1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa', // Genesis address
'1BvBMSEYstWetqTFn5Au4m4GFg7xJaNVN2', // Your address
};
final wallet = SPVWallet(addresses);
// Get current chain tip from storage
final storedHeight = await getStoredChainTip() ?? 0;
final peer = Peer(
address: 'bitcoin-node.example.com',
port: 8333,
network: BitcoinNetwork.mainnet,
handler: wallet,
config: PeerConfig(
startHeight: storedHeight, // Resume from last sync
userAgent: '/my-spv-wallet:1.0/',
enablePingPong: true,
),
);
await peer.connect();
print('📱 SPV wallet connected and syncing...');
// Keep wallet running
await Future.delayed(Duration.infinity);
}class MiningPoolConnector implements PeerHandlerI {
final String poolAddress;
MiningPoolConnector(this.poolAddress);
@override
Future<void> handleBlockAnnouncement(InvVect msg, PeerI peer) async {
// Request full block for mining template
final getBlock = MsgGetData([msg]);
await peer.writeMessage(getBlock);
}
@override
Future<void> handleBlock(WireMessage msg, PeerI peer) async {
final block = msg as MsgBlock;
// Forward to mining pool
await forwardToPool(block);
// Update mining template
await updateMiningTemplate(block.header);
}
Future<void> submitBlock(MsgBlock block) async {
// Submit solved block to network
final peers = await getPeers();
for (final peer in peers) {
await peer.writeMessage(block);
}
}
}void main() {
initializeMessages(); // First thing in main()
// ... rest of application
}// For SPV wallets
const spvConfig = PeerConfig(
startHeight: currentChainTip,
services: ServiceFlags.none,
relay: false,
);
// For full nodes
const fullNodeConfig = PeerConfig(
services: ServiceFlags.nodeNetwork,
relay: true,
);try {
await peer.connect();
} on ConnectionTimeoutException {
// Try different peer
} on ConnectionException catch (e) {
// Log error and retry
}peer.stateStream.listen((state) {
if (state == PeerState.unhealthy) {
// Replace unhealthy peer
await replacePeer(peer);
}
});class SafeHandler implements PeerHandlerI {
@override
Future<void> handleTransaction(WireMessage msg, PeerI peer) async {
try {
await processTransaction(msg as MsgTx);
} catch (e) {
logger.warning('Transaction processing failed: $e');
// Don't crash the application
}
}
}For more examples and advanced usage patterns, see the examples directory.